use crate::{
abi::{self, FastlyStatus},
http::{
header::{HeaderName, HeaderValue},
request::{
handle::{redirect_to_grip_proxy, redirect_to_websocket_proxy, RequestHandle},
CacheKeyGen, Request, SendError, SendErrorCause,
},
response::assert_single_downstream_response_is_sent,
},
Backend, Error,
};
use anyhow::anyhow;
use fastly_sys::fastly_backend;
use sha2::{Digest, Sha256};
use std::sync::Arc;
#[doc(inline)]
pub use fastly_sys::fastly_backend::BackendHealth;
pub use crate::backend::{BackendBuilder, BackendCreationError};
#[doc = include_str!("../docs/snippets/experimental.md")]
pub fn uap_parse(
user_agent: &str,
) -> Result<(String, Option<String>, Option<String>, Option<String>), Error> {
let user_agent: &[u8] = user_agent.as_ref();
let max_length = 255;
let mut family = Vec::with_capacity(max_length);
let mut major = Vec::with_capacity(max_length);
let mut minor = Vec::with_capacity(max_length);
let mut patch = Vec::with_capacity(max_length);
let mut family_nwritten = 0;
let mut major_nwritten = 0;
let mut minor_nwritten = 0;
let mut patch_nwritten = 0;
let status = unsafe {
abi::fastly_uap::parse(
user_agent.as_ptr(),
user_agent.len(),
family.as_mut_ptr(),
family.capacity(),
&mut family_nwritten,
major.as_mut_ptr(),
major.capacity(),
&mut major_nwritten,
minor.as_mut_ptr(),
minor.capacity(),
&mut minor_nwritten,
patch.as_mut_ptr(),
patch.capacity(),
&mut patch_nwritten,
)
};
if status.is_err() {
return Err(Error::msg("fastly_uap::parse failed"));
}
assert!(
family_nwritten <= family.capacity(),
"fastly_uap::parse wrote too many bytes for family"
);
unsafe {
family.set_len(family_nwritten);
}
assert!(
major_nwritten <= major.capacity(),
"fastly_uap::parse wrote too many bytes for major"
);
unsafe {
major.set_len(major_nwritten);
}
assert!(
minor_nwritten <= minor.capacity(),
"fastly_uap::parse wrote too many bytes for minor"
);
unsafe {
minor.set_len(minor_nwritten);
}
assert!(
patch_nwritten <= patch.capacity(),
"fastly_uap::parse wrote too many bytes for patch"
);
unsafe {
patch.set_len(patch_nwritten);
}
Ok((
String::from_utf8_lossy(&family).to_string(),
Some(String::from_utf8_lossy(&major).to_string()),
Some(String::from_utf8_lossy(&minor).to_string()),
Some(String::from_utf8_lossy(&patch).to_string()),
))
}
#[doc = include_str!("../docs/snippets/experimental.md")]
pub trait RequestCacheKey {
#[doc = include_str!("../docs/snippets/experimental.md")]
fn set_cache_key(&mut self, key: [u8; 32]);
#[doc = include_str!("../docs/snippets/experimental.md")]
fn with_cache_key(self, key: [u8; 32]) -> Self;
#[doc = include_str!("../docs/snippets/experimental.md")]
fn set_cache_key_fn(&mut self, f: impl Fn(&Request) -> [u8; 32] + Send + Sync + 'static);
#[doc = include_str!("../docs/snippets/experimental.md")]
fn with_cache_key_fn(self, f: impl Fn(&Request) -> [u8; 32] + Send + Sync + 'static) -> Self;
#[doc = include_str!("../docs/snippets/experimental.md")]
fn set_cache_key_str(&mut self, key_str: impl AsRef<[u8]>);
#[doc = include_str!("../docs/snippets/experimental.md")]
fn with_cache_key_str(self, key_str: impl AsRef<[u8]>) -> Self;
}
impl RequestCacheKey for Request {
#[doc = include_str!("../docs/snippets/experimental.md")]
fn set_cache_key(&mut self, key: [u8; 32]) {
self.cache_key = Some(CacheKeyGen::Set(key));
}
#[doc = include_str!("../docs/snippets/experimental.md")]
fn with_cache_key(mut self, key: [u8; 32]) -> Self {
self.set_cache_key(key);
self
}
#[doc = include_str!("../docs/snippets/experimental.md")]
fn set_cache_key_fn(&mut self, f: impl Fn(&Request) -> [u8; 32] + Send + Sync + 'static) {
self.cache_key = Some(CacheKeyGen::Lazy(Arc::new(f)));
}
#[doc = include_str!("../docs/snippets/experimental.md")]
fn with_cache_key_fn(
mut self,
f: impl Fn(&Request) -> [u8; 32] + Send + Sync + 'static,
) -> Self {
self.set_cache_key_fn(f);
self
}
#[doc = include_str!("../docs/snippets/experimental.md")]
fn set_cache_key_str(&mut self, key_str: impl AsRef<[u8]>) {
let mut sha = Sha256::new();
sha.update(key_str);
sha.update(b"\x00\xf0\x9f\xa7\x82\x00"); self.set_cache_key(*sha.finalize().as_ref())
}
#[doc = include_str!("../docs/snippets/experimental.md")]
fn with_cache_key_str(mut self, key_str: impl AsRef<[u8]>) -> Self {
self.set_cache_key_str(key_str);
self
}
}
#[doc = include_str!("../docs/snippets/experimental.md")]
pub trait RequestHandleCacheKey {
#[doc = include_str!("../docs/snippets/experimental.md")]
fn set_cache_key(&mut self, key: &[u8; 32]);
}
impl RequestHandleCacheKey for RequestHandle {
#[doc = include_str!("../docs/snippets/experimental.md")]
fn set_cache_key(&mut self, key: &[u8; 32]) {
const DIGITS: &[u8; 16] = b"0123456789ABCDEF";
let mut hex = [0; 64];
for (i, b) in key.iter().enumerate() {
hex[i * 2] = DIGITS[(b >> 4) as usize];
hex[i * 2 + 1] = DIGITS[(b & 0xf) as usize];
}
self.insert_header(
&HeaderName::from_static("fastly-xqd-cache-key"),
&HeaderValue::from_bytes(&hex).unwrap(),
)
}
}
pub trait RequestUpgradeWebsocket {
fn handoff_websocket(self, backend: &str) -> Result<(), SendError>;
fn handoff_fanout(self, backend: &str) -> Result<(), SendError>;
}
impl RequestUpgradeWebsocket for Request {
fn handoff_websocket(self, backend: &str) -> Result<(), SendError> {
assert_single_downstream_response_is_sent(true);
let req_handle = self.make_request_handle();
let status = redirect_to_websocket_proxy(req_handle, backend);
if status.is_err() {
Err(SendError::new(
backend,
self,
SendErrorCause::status(status),
))
} else {
Ok(())
}
}
fn handoff_fanout(self, backend: &str) -> Result<(), SendError> {
assert_single_downstream_response_is_sent(true);
let req_handle = self.make_request_handle();
let status = redirect_to_grip_proxy(req_handle, backend);
if status.is_err() {
Err(SendError::new(
backend,
self,
SendErrorCause::status(status),
))
} else {
Ok(())
}
}
}
pub trait RequestHandleUpgradeWebsocket {
fn handoff_websocket(&mut self, backend: &str) -> Result<(), SendErrorCause>;
fn handoff_fanout(&mut self, backend: &str) -> Result<(), SendErrorCause>;
}
impl RequestHandleUpgradeWebsocket for RequestHandle {
fn handoff_websocket(&mut self, backend: &str) -> Result<(), SendErrorCause> {
match unsafe {
abi::fastly_http_req::redirect_to_websocket_proxy_v2(
self.as_u32(),
backend.as_ptr(),
backend.len(),
)
} {
FastlyStatus::OK => Ok(()),
status => Err(SendErrorCause::status(status)),
}
}
fn handoff_fanout(&mut self, backend: &str) -> Result<(), SendErrorCause> {
match unsafe {
abi::fastly_http_req::redirect_to_grip_proxy_v2(
self.as_u32(),
backend.as_ptr(),
backend.len(),
)
} {
FastlyStatus::OK => Ok(()),
status => Err(SendErrorCause::status(status)),
}
}
}
pub trait BackendExt {
#[deprecated(
since = "0.9.3",
note = "The BackendExt::builder trait method is now part of Backend."
)]
#[doc = include_str!("../docs/snippets/dynamic-backend-builder.md")]
fn builder(name: impl ToString, target: impl ToString) -> BackendBuilder;
fn is_healthy(&self) -> Result<BackendHealth, Error>;
}
impl BackendExt for Backend {
fn builder(name: impl ToString, target: impl ToString) -> BackendBuilder {
BackendBuilder::new(name.to_string(), target.to_string())
}
fn is_healthy(&self) -> Result<BackendHealth, Error> {
let mut backend_health_out = BackendHealth::Unknown;
unsafe {
fastly_backend::is_healthy(
self.name().as_ptr(),
self.name().len(),
&mut backend_health_out,
)
}
.result()
.map_err(|e| match e {
FastlyStatus::NONE => anyhow!("backend not found"),
_ => anyhow!("backend healthcheck error: {:?}", e),
})?;
Ok(backend_health_out)
}
}
#[doc = include_str!("../docs/snippets/experimental.md")]
pub trait GrpcBackend {
#[doc = include_str!("../docs/snippets/experimental.md")]
fn for_grpc(self, value: bool) -> Self;
}