#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(
unreachable_pub,
missing_debug_implementations,
missing_docs,
clippy::pedantic
)]
#![allow(
// I WANT A LONG fn!
clippy::too_many_lines,
// I know what I'm doing with unwraps. They should all be motivated.
clippy::missing_panics_doc,
// When a parameter of a function is prefixed due to `#[cfg]` in an fn.
clippy::used_underscore_binding,
// Same as ↑.
clippy::unused_self,
// When a enum variant has been conditionally compiled away.
irrefutable_let_patterns,
)]
#![doc(html_favicon_url = "https://kvarn.org/favicon.svg")]
#![doc(html_root_url = "https://doc.kvarn.org/")]
pub mod application;
pub mod comprash;
pub mod cors;
pub mod csp;
pub mod ctl;
pub mod encryption;
pub mod error;
pub mod extensions;
pub mod host;
pub mod limiting;
pub mod prelude;
pub mod read;
pub mod shutdown;
pub mod vary;
pub mod websocket;
use prelude::{chrono::*, internals::*, networking::*, *};
pub use error::{default as default_error, default_response as default_error_response};
pub use extensions::{Extensions, Id};
pub use read::{file as read_file, file_cached as read_file_cached};
#[derive(Debug)]
#[must_use = "must start a server if creating a config"]
pub struct RunConfig {
ports: Vec<PortDescriptor>,
ctl: bool,
ctl_path: Option<PathBuf>,
plugins: ctl::Plugins,
}
impl RunConfig {
pub fn new() -> Self {
RunConfig {
ports: vec![],
ctl: true,
ctl_path: None,
plugins: ctl::Plugins::default(),
}
}
pub fn bind(mut self, port: PortDescriptor) -> Self {
self.ports.push(port);
self
}
pub fn disable_ctl(mut self) -> Self {
self.ctl = false;
self
}
pub fn set_ctl_path(mut self, path: impl AsRef<Path>) -> Self {
self.ctl_path = Some(path.as_ref().to_path_buf());
self
}
pub fn add_plugin(mut self, name: impl AsRef<str>, plugin: ctl::Plugin) -> Self {
self.plugins.add_plugin(name, plugin);
self
}
pub async fn execute(self) -> Arc<shutdown::Manager> {
let RunConfig {
ports,
ctl,
ctl_path,
plugins,
} = self;
info!("Starting server on {} ports.", ports.len());
let len = ports.len();
let mut shutdown_manager = shutdown::Manager::new(len);
let ports_clone = Arc::new(ports.clone());
let mut listeners = Vec::with_capacity(len * 2);
for descriptor in ports {
fn create_listener(
create_socket: impl Fn() -> TcpSocket,
address: SocketAddr,
shutdown_manager: &mut shutdown::Manager,
) -> AcceptManager {
let socket = create_socket();
#[cfg(all(unix, not(target_os = "solaris"), not(target_os = "illumos")))]
{
if socket.set_reuseaddr(true).is_err() || socket.set_reuseport(true).is_err() {
error!("Failed to set reuse address/port. This is needed for graceful shutdown handover.");
}
}
socket.bind(address).expect("Failed to bind address");
let listener = socket
.listen(1024)
.expect("Failed to listen on bound address.");
shutdown_manager.add_listener(listener)
}
let descriptor = Arc::new(descriptor);
if matches!(descriptor.version, BindIpVersion::V4 | BindIpVersion::Both) {
let listener = create_listener(
|| {
TcpSocket::new_v4()
.expect("Failed to create a new IPv4 socket configuration")
},
SocketAddr::new(IpAddr::V4(net::Ipv4Addr::UNSPECIFIED), descriptor.port),
&mut shutdown_manager,
);
listeners.push((listener, Arc::clone(&descriptor)));
}
if matches!(descriptor.version, BindIpVersion::V6 | BindIpVersion::Both) {
let listener = create_listener(
|| {
TcpSocket::new_v6()
.expect("Failed to create a new IPv6 socket configuration")
},
SocketAddr::new(IpAddr::V6(net::Ipv6Addr::UNSPECIFIED), descriptor.port),
&mut shutdown_manager,
);
listeners.push((listener, descriptor));
}
}
let shutdown_manager = shutdown_manager.build();
if ctl {
#[cfg(any(
not(feature = "graceful-shutdown"),
target_os = "illumos",
target_os = "solaris"
))]
ctl::listen(
plugins,
ports_clone,
Arc::clone(&shutdown_manager),
ctl_path,
)
.await;
}
for (listener, descriptor) in listeners {
let shutdown_manager = Arc::clone(&shutdown_manager);
let future = async move {
accept(listener, descriptor, &shutdown_manager)
.await
.expect("Failed to accept message!");
};
tokio::spawn(future);
}
if ctl {
#[cfg(all(
feature = "graceful-shutdown",
not(target_os = "illumos"),
not(target_os = "solaris")
))]
ctl::listen(
plugins,
ports_clone,
Arc::clone(&shutdown_manager),
ctl_path,
)
.await;
}
shutdown_manager
}
}
impl Default for RunConfig {
fn default() -> Self {
Self::new()
}
}
#[macro_export]
macro_rules! run_config {
($($port_descriptor:expr),+ $(,)?) => {
$crate::RunConfig::new()$(.bind($port_descriptor))+
};
}
macro_rules! ret_log_app_error {
($e:expr) => {
match $e {
Err(err) => {
if let application::Error::ClientRefusedResponse = &err {
return Ok(());
}
error!("An error occurred while sending a response. {:?}", &err);
return Err(err.into());
}
Ok(val) => val,
}
};
}
async fn accept(
mut listener: AcceptManager,
descriptor: Arc<PortDescriptor>,
shutdown_manager: &Arc<shutdown::Manager>,
) -> Result<(), io::Error> {
trace!(
"Started listening on {:?}",
listener.get_inner().local_addr()
);
loop {
match listener.accept(shutdown_manager).await {
AcceptAction::Shutdown => {
debug!("Closing listener.");
return Ok(());
}
AcceptAction::Accept(result) => match result {
Ok((socket, addr)) => {
match descriptor.data.limiter().register(addr.ip()) {
LimitAction::Drop => {
drop(socket);
return Ok(());
}
LimitAction::Send | LimitAction::Passed => {}
}
let descriptor = Arc::clone(&descriptor);
#[cfg(feature = "graceful-shutdown")]
let shutdown_manager = Arc::clone(shutdown_manager);
tokio::spawn(async move {
#[cfg(feature = "graceful-shutdown")]
shutdown_manager.add_connection();
let _result = handle_connection(socket, addr, descriptor, || {
#[cfg(feature = "graceful-shutdown")]
{
!shutdown_manager.get_shutdown(threading::Ordering::Relaxed)
}
#[cfg(not(feature = "graceful-shutdown"))]
{
true
}
})
.await;
#[cfg(feature = "graceful-shutdown")]
shutdown_manager.remove_connection();
});
continue;
}
Err(err) => {
#[cfg(feature = "graceful-shutdown")]
let connections = format!(
" {} current connections.",
shutdown_manager.get_connecions()
);
#[cfg(not(feature = "graceful-shutdown"))]
let connections = "";
error!("Failed to accept() on listener.{connections}");
return Err(err);
}
},
}
}
}
pub async fn handle_connection(
stream: TcpStream,
address: SocketAddr,
descriptor: Arc<PortDescriptor>,
mut continue_accepting: impl FnMut() -> bool,
) -> io::Result<()> {
#[cfg(feature = "https")]
let encrypted = {
encryption::Encryption::new_tcp(stream, descriptor.server_config.clone())
.await
.map_err(|err| match err {
encryption::Error::Io(io) => io,
encryption::Error::Tls(tls) => io::Error::new(io::ErrorKind::InvalidData, tls),
})
}?;
#[cfg(not(feature = "https"))]
let encrypted = encryption::Encryption::new_tcp(stream);
let version =
match encrypted.alpn_protocol() {
Some(b"h2") => Version::HTTP_2,
None | Some(b"http/1.1") => Version::HTTP_11,
Some(b"http/1.0") => Version::HTTP_10,
Some(b"http/0.9") => Version::HTTP_09,
_ => return Err(io::Error::new(
io::ErrorKind::InvalidData,
"HTTP version not supported. Something is probably wrong with your alpn config.",
)),
};
let hostname = encrypted.sni_hostname().map(str::to_string);
debug!("New connection requesting hostname '{:?}'", hostname);
let mut http = application::HttpConnection::new(encrypted, version)
.await
.map_err::<io::Error, _>(application::Error::into)?;
info!("Accepting requests from {}", address);
while let Ok((mut request, mut response_pipe)) = http
.accept(
descriptor
.data
.get_default()
.map(|host| host.name.as_bytes()),
)
.await
{
debug!("We got a new request on connection.");
trace!("Got request {:#?}", request);
let host = if let Some(host) = descriptor
.data
.get_from_request(&request, hostname.as_deref())
{
host
} else {
info!(
"Failed to get host: {}",
utils::parse::Error::NoHost.as_str()
);
return Ok(());
};
match host.limiter.register(address.ip()) {
LimitAction::Drop => return Ok(()),
LimitAction::Send => {
let (mut response, body) = utils::split_response(limiting::get_too_many_requests());
response_pipe.ensure_version_and_length(&mut response, body.len());
let mut body_pipe =
ret_log_app_error!(response_pipe.send_response(response, false).await);
ret_log_app_error!(body_pipe.send_with_maybe_close(body, true).await);
continue;
}
LimitAction::Passed => {}
}
debug!("Accepting new connection from {} on {}", address, host.name);
debug_assert!(descriptor.data.get_host(&host.name).is_some());
let hostname = unsafe { utils::SuperUnsafePointer::new(&host.name) };
let moved_host_collection = Arc::clone(&descriptor.data);
let future = async move {
let hostname = unsafe { hostname.get() };
let host = moved_host_collection.get_host(hostname).unwrap();
let response = handle_cache(&mut request, address, host).await;
if let Err(err) = SendKind::Send(&mut response_pipe)
.send(response, &request, host, address)
.await
{
error!("Got error from writing response: {:?}", err);
}
drop(request);
drop(response_pipe);
};
match version {
Version::HTTP_09 | Version::HTTP_10 | Version::HTTP_11 => future.await,
_ => {
tokio::spawn(future);
}
}
if !continue_accepting() {
break;
}
}
debug!("Connection finished.");
http.shutdown().await;
Ok(())
}
#[derive(Debug)]
pub enum SendKind<'a> {
Send(&'a mut application::ResponsePipe),
Push(&'a mut application::PushedResponsePipe),
}
impl<'a> SendKind<'a> {
#[inline]
pub fn ensure_version_and_length<T>(&self, response: &mut Response<T>, len: usize) {
match self {
Self::Send(p) => p.ensure_version_and_length(response, len),
Self::Push(p) => p.ensure_version(response),
}
}
#[inline]
#[allow(clippy::too_many_arguments)]
pub async fn send(
&mut self,
response: CacheReply,
request: &FatRequest,
host: &Host,
address: SocketAddr,
) -> io::Result<()> {
let CacheReply {
mut response,
identity_body,
sanitize_data: data,
future,
} = response;
if let Ok(data) = &data {
match data.apply_to_response(&mut response).await {
Err(SanitizeError::RangeNotSatisfiable) => {
response = default_error(
StatusCode::RANGE_NOT_SATISFIABLE,
Some(host),
Some(b"Range start after end of body"),
)
.await;
}
Err(SanitizeError::UnsafePath) => {
response = default_error(StatusCode::BAD_REQUEST, Some(host), None).await;
}
Ok(()) => {}
}
}
let len = response.body().len();
self.ensure_version_and_length(&mut response, len);
let (mut response, body) = utils::split_response(response);
host.extensions
.resolve_package(&mut response, request, host)
.await;
match self {
SendKind::Send(response_pipe) => {
let mut body_pipe =
ret_log_app_error!(response_pipe.send_response(response, false).await);
if utils::method_has_response_body(request.method()) || !body.is_empty() {
ret_log_app_error!(body_pipe.send_with_maybe_close(body, false).await);
}
if let Some(mut future) = future {
future.call(&mut body_pipe, host).await;
}
host.extensions
.resolve_post(request, identity_body, response_pipe, address, host)
.await;
ret_log_app_error!(body_pipe.close().await);
}
SendKind::Push(push_pipe) => {
let send_body =
utils::method_has_response_body(request.method()) || !body.is_empty();
let mut body_pipe = ret_log_app_error!(
push_pipe.send_response(response, !send_body && future.is_none())
);
if send_body {
ret_log_app_error!(
body_pipe
.send_with_maybe_close(body, future.is_none())
.await
);
}
if let Some(mut future) = future {
future.call(&mut body_pipe, host).await;
}
if !send_body {
ret_log_app_error!(body_pipe.close().await);
}
}
}
Ok(())
}
}
pub struct CacheReply {
pub response: Response<Bytes>,
pub identity_body: Bytes,
pub sanitize_data: Result<utils::CriticalRequestComponents, SanitizeError>,
pub future: Option<ResponsePipeFuture>,
}
impl Debug for CacheReply {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct(utils::ident_str!(CacheReply));
utils::fmt_fields!(
s,
(self.response),
(self.identity_body),
(self.sanitize_data),
(self.future, &"[internal future]".as_clean())
);
s.finish()
}
}
mod handle_cache_helpers {
use crate::prelude::*;
pub(super) async fn get_response(
request: &mut FatRequest,
host: &Host,
sanitize_data: &Result<utils::CriticalRequestComponents, SanitizeError>,
address: SocketAddr,
overide_uri: Option<&Uri>,
) -> (
comprash::CompressedResponse,
comprash::ClientCachePreference,
comprash::ServerCachePreference,
Option<ResponsePipeFuture>,
comprash::PathQuery,
) {
let path_query = comprash::PathQuery::from(request.uri());
let (mut resp, mut client_cache, mut server_cache, compress, future) =
match sanitize_data {
Ok(_) => {
let path = if host.options.disable_fs {
None
} else if let Ok(decoded) =
percent_encoding::percent_decode_str(request.uri().path()).decode_utf8()
{
Some(utils::make_path(
&host.path,
host.options
.public_data_dir
.as_deref()
.unwrap_or_else(|| Path::new("public")),
utils::parse::uri(&decoded).unwrap(),
None,
))
} else {
warn!("Invalid percent encoding in path.");
None
};
handle_request(request, overide_uri, address, host, &path).await
}
Err(err) => error::sanitize_error_into_response(*err, host).await,
}
.into_parts();
host.extensions
.resolve_present(
request,
&mut resp,
&mut client_cache,
&mut server_cache,
host,
address,
)
.await;
let extension = match Path::new(request.uri().path())
.extension()
.and_then(std::ffi::OsStr::to_str)
{
Some(ext) => ext,
None => match host.options.extension_default.as_ref() {
Some(ext) => ext.as_str(),
None => "",
},
};
(
comprash::CompressedResponse::new(
resp,
compress,
client_cache,
extension,
host.options.disable_client_cache,
),
client_cache,
server_cache,
future,
path_query,
)
}
pub(super) async fn maybe_cache<T>(
host: &Host,
server_cache: comprash::ServerCachePreference,
path_query: PathQuery,
response: VariedResponse,
method: &Method,
future: &Option<T>,
) -> Option<VariedResponse> {
if future.is_none() {
if let Some(response_cache) = &host.response_cache {
let cache_action = (host.options.status_code_cache_filter)(
response.first().0.get_identity().status(),
);
if server_cache.cache(cache_action, method) {
let mut lock = response_cache.write().await;
let key = if server_cache.query_matters() {
comprash::UriKey::PathQuery(path_query)
} else {
comprash::UriKey::Path(path_query.into_path())
};
info!("Caching uri {:?}!", &key);
lock.cache(key, response);
return None;
}
}
} else {
info!("Not caching; a Prepare extension has captured. If we cached, it would not be called again.");
}
Some(response)
}
pub(super) async fn handle_vary_missing(
request: &mut FatRequest,
host: &Host,
sanitize_data: &Result<utils::CriticalRequestComponents, SanitizeError>,
address: SocketAddr,
overide_uri: Option<&Uri>,
uri_key: UriKey,
params: vary::CacheParams,
) -> (
Arc<(comprash::CompressedResponse, vary::HeaderCollection)>,
Option<extensions::ResponsePipeFuture>,
) {
let (compressed_response, _, server_cache, future, path_query) =
get_response(request, host, sanitize_data, address, overide_uri).await;
let mut lock = if let Some(response_cache) = &host.response_cache {
Some(response_cache.write().await)
} else {
None
};
let cached = if let Some(lock) = &mut lock {
{
match lock.get_mut_with_lifetime(&uri_key).into_option() {
Some(t) => (uri_key, Some(t)),
None => match uri_key {
UriKey::Path(_) => (uri_key, None),
UriKey::PathQuery(path_query) => {
let uri_key = UriKey::Path(path_query.into_path());
let result = lock.get_mut_with_lifetime(&uri_key).into_option();
(uri_key, result)
}
},
}
}
} else {
(uri_key, None)
};
let arc = match cached {
(_, Some((resp, _))) => Arc::clone(resp.push_response(compressed_response, params)),
(_, None) => {
let vary_rules = host.vary.rules_from_request(request);
let varied_response = unsafe {
VariedResponse::new(compressed_response, request, vary_rules.as_ref())
};
let arc = Arc::clone(varied_response.first());
handle_cache_helpers::maybe_cache(
host,
server_cache,
path_query,
varied_response,
request.method(),
&future,
)
.await;
arc
}
};
(arc, future)
}
}
pub async fn handle_cache(
request: &mut FatRequest,
address: SocketAddr,
host: &Host,
) -> CacheReply {
let sanitize_data = utils::sanitize_request(request);
let overide_uri = host.extensions.resolve_prime(request, host, address).await;
let uri_key =
comprash::UriKey::path_and_query(overide_uri.as_ref().unwrap_or_else(|| request.uri()));
let mut lock = if let Some(response_cache) = &host.response_cache {
Some(response_cache.read().await)
} else {
None
};
let cached = if let Some(lock) = &mut lock {
uri_key.call_all(|key| lock.get_with_lifetime(key).into_option())
} else {
(uri_key, None)
};
#[allow(clippy::single_match_else, clippy::unnested_or_patterns)]
let (response, identity, future) = match cached {
(uri_key, Some((resp, (creation, _))))
if sanitize_data.is_ok()
&& matches!(request.method(), &Method::GET | &Method::HEAD) =>
{
debug!("Found in cache!");
let creation = *creation;
let if_modified_since: Option<OffsetDateTime> =
if host.options.disable_if_modified_since {
None
} else {
request
.headers()
.get("if-modified-since")
.and_then(|h| h.to_str().ok())
.and_then(|s| {
time::PrimitiveDateTime::parse(s, &comprash::HTTP_DATE)
.ok()
.map(time::PrimitiveDateTime::assume_utc)
})
};
let client_request_is_fresh = if_modified_since.map_or(false, |timestamp| {
timestamp >= creation - 1.seconds()
});
let mut response_data = if client_request_is_fresh {
drop(lock);
let mut response = Response::new(Bytes::new());
*response.status_mut() = StatusCode::NOT_MODIFIED;
(response, Bytes::new(), None)
} else {
let (resp_vary, future) = match resp.get_by_request(request) {
Ok(arc) => {
let arc = Arc::clone(arc);
drop(lock);
(arc, None)
}
Err(params) => {
drop(lock);
handle_cache_helpers::handle_vary_missing(
request,
host,
&sanitize_data,
address,
overide_uri.as_ref(),
uri_key,
params,
)
.await
}
};
let (resp, vary) = &*resp_vary;
let mut response = match resp.clone_preferred(request, &host.compression_options) {
Err(message) => {
error::default(
StatusCode::NOT_ACCEPTABLE,
Some(host),
Some(message.as_bytes()),
)
.await
}
Ok(response) => response,
};
vary::apply_header(&mut response, vary);
let identity_body = Bytes::clone(resp.get_identity().body());
(response, identity_body, future)
};
if !host.options.disable_if_modified_since {
let last_modified = HeaderValue::from_str(
&creation
.format(&comprash::HTTP_DATE)
.expect("failed to format datetime"),
)
.expect("We know these bytes are valid.");
response_data
.0
.headers_mut()
.insert("last-modified", last_modified);
}
response_data
}
_ => {
drop(lock);
let sanitize_data = &sanitize_data;
let overide_uri = overide_uri.as_ref();
let (compressed_response, _, server_cache, future, path_query) =
handle_cache_helpers::get_response(
request,
host,
sanitize_data,
address,
overide_uri,
)
.await;
let vary_rules = host.vary.rules_from_request(request);
let varied_response =
unsafe { VariedResponse::new(compressed_response, request, vary_rules.as_ref()) };
let compressed_response = &varied_response.first().0;
let mut response =
match compressed_response.clone_preferred(request, &host.compression_options) {
Err(message) => {
error::default(
StatusCode::NOT_ACCEPTABLE,
Some(host),
Some(message.as_bytes()),
)
.await
}
Ok(response) => response,
};
let identity_body = Bytes::clone(compressed_response.get_identity().body());
let vary = &varied_response.first().1;
vary::apply_header(&mut response, vary);
let cache_rejected = handle_cache_helpers::maybe_cache(
host,
server_cache,
path_query,
varied_response,
request.method(),
&future,
)
.await;
if !host.options.disable_if_modified_since && cache_rejected.is_none() {
let last_modified = HeaderValue::from_str(
&OffsetDateTime::now_utc()
.format(&comprash::HTTP_DATE)
.expect("failed to format datetime"),
)
.expect("We know these bytes are valid.");
response
.headers_mut()
.insert("last-modified", last_modified);
}
(response, identity_body, future)
}
};
CacheReply {
response,
identity_body: identity,
sanitize_data,
future,
}
}
pub async fn handle_request(
request: &mut FatRequest,
overide_uri: Option<&Uri>,
address: SocketAddr,
host: &Host,
path: &Option<PathBuf>,
) -> FatResponse {
let mut response = None;
let mut client_cache = None;
let mut server_cache = None;
let mut compress = None;
let mut future = None;
#[allow(unused_mut)]
let mut status = None;
{
if let Some(resp) = host
.extensions
.resolve_prepare(request, overide_uri, host, path, address)
.await
{
let resp = resp.into_parts();
response.replace(resp.0);
client_cache.replace(resp.1);
server_cache.replace(resp.2);
compress.replace(resp.3);
if let Some(f) = resp.4 {
future.replace(f);
}
}
}
if response.is_none() {
if let Some(path) = path {
match *request.method() {
Method::GET | Method::HEAD => {
if let Some(content) = read_file(&path, host.file_cache.as_ref()).await {
response = Some(Response::new(content));
}
}
_ => status = Some(StatusCode::METHOD_NOT_ALLOWED),
}
}
}
let response = match response {
Some(r) => r,
None => {
error::default_response(status.unwrap_or(StatusCode::NOT_FOUND), host, None)
.await
.response
}
};
macro_rules! maybe_with {
($response: expr, $option: expr, $method: tt) => {
if let Some(t) = $option {
$response = $response.$method(t);
}
};
}
let mut response = FatResponse::cache(response);
maybe_with!(response, client_cache, with_client_cache);
maybe_with!(response, server_cache, with_server_cache);
maybe_with!(response, compress, with_compress);
maybe_with!(response, future, with_future);
response
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[must_use]
pub enum BindIpVersion {
V4,
V6,
Both,
}
#[derive(Clone)]
#[must_use]
pub struct PortDescriptor {
port: u16,
#[cfg(feature = "https")]
server_config: Option<Arc<rustls::ServerConfig>>,
data: Arc<HostCollection>,
version: BindIpVersion,
}
impl PortDescriptor {
pub fn http(host_data: Arc<HostCollection>) -> Self {
Self {
port: 80,
#[cfg(feature = "https")]
server_config: None,
data: host_data,
version: BindIpVersion::Both,
}
}
#[cfg(feature = "https")]
pub fn https(host_data: Arc<HostCollection>) -> Self {
Self {
port: 443,
server_config: Some(Arc::new(host_data.make_config())),
data: host_data,
version: BindIpVersion::Both,
}
}
#[cfg(feature = "https")]
pub fn with_server_config(
port: u16,
host_data: Arc<HostCollection>,
server_config: Option<Arc<rustls::ServerConfig>>,
) -> Self {
Self {
port,
server_config,
data: host_data,
version: BindIpVersion::Both,
}
}
pub fn new(port: u16, host_data: Arc<HostCollection>) -> Self {
Self {
port,
#[cfg(feature = "https")]
server_config: Some(Arc::new(host_data.make_config())),
data: host_data,
version: BindIpVersion::Both,
}
}
pub fn unsecure(port: u16, host_data: Arc<HostCollection>) -> Self {
Self {
port,
#[cfg(feature = "https")]
server_config: None,
data: host_data,
version: BindIpVersion::Both,
}
}
pub fn ipv4_only(mut self) -> Self {
self.version = BindIpVersion::V4;
self
}
pub fn ipv6_only(mut self) -> Self {
self.version = BindIpVersion::V6;
self
}
}
impl PortDescriptor {
#[must_use]
pub fn port(&self) -> u16 {
self.port
}
#[cfg(feature = "https")]
#[must_use]
pub fn tls_config(&self) -> Option<&rustls::ServerConfig> {
self.server_config.as_deref()
}
pub fn hosts(&self) -> &HostCollection {
&self.data
}
pub fn internet_protocol(&self) -> BindIpVersion {
self.version
}
}
impl Debug for PortDescriptor {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct(utils::ident_str!(PortDescriptor));
utils::fmt_fields!(
s,
(self.port),
#[cfg(feature = "https")]
(
self.server_config,
&self
.server_config
.as_ref()
.map(|_| "[opaque certificate]".as_clean())
),
(self.data),
(self.version),
);
s.finish()
}
}
pub type FatRequest = Request<application::Body>;
#[must_use = "send the response"]
pub struct FatResponse {
response: Response<Bytes>,
client: comprash::ClientCachePreference,
server: comprash::ServerCachePreference,
compress: comprash::CompressPreference,
future: Option<ResponsePipeFuture>,
}
impl FatResponse {
pub fn new(
response: Response<Bytes>,
server_cache_preference: comprash::ServerCachePreference,
) -> Self {
Self {
response,
client: comprash::ClientCachePreference::Full,
server: server_cache_preference,
compress: comprash::CompressPreference::Full,
future: None,
}
}
pub fn cache(response: Response<Bytes>) -> Self {
Self::new(response, comprash::ServerCachePreference::Full)
}
pub fn no_cache(response: Response<Bytes>) -> Self {
Self {
response,
client: comprash::ClientCachePreference::None,
server: comprash::ServerCachePreference::None,
compress: comprash::CompressPreference::Full,
future: None,
}
}
pub fn with_client_cache(mut self, preference: comprash::ClientCachePreference) -> Self {
self.client = preference;
self
}
pub fn with_server_cache(mut self, preference: comprash::ServerCachePreference) -> Self {
self.server = preference;
self
}
pub fn with_compress(mut self, preference: comprash::CompressPreference) -> Self {
self.compress = preference;
self
}
pub fn with_future(mut self, future: ResponsePipeFuture) -> Self {
self.future = Some(future);
self
}
pub fn with_content_type(mut self, content_type: &Mime) -> Self {
self.response.headers_mut().insert(
"content-type",
HeaderValue::from_maybe_shared::<Bytes>(content_type.to_string().into_bytes().into())
.unwrap(),
);
self
}
pub fn into_parts(
self,
) -> (
Response<Bytes>,
comprash::ClientCachePreference,
comprash::ServerCachePreference,
comprash::CompressPreference,
Option<ResponsePipeFuture>,
) {
(
self.response,
self.client,
self.server,
self.compress,
self.future,
)
}
}
impl Debug for FatResponse {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
enum BytesOrStr<'a> {
Str(&'a str),
Bytes(&'a [u8]),
}
impl<'a> Debug for BytesOrStr<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Str(s) => f.write_str(s),
Self::Bytes(_) => f.write_str("[binary data]"),
}
}
}
let response = utils::empty_clone_response(&self.response);
let body = if let Ok(s) = str::from_utf8(self.response.body()) {
BytesOrStr::Str(s)
} else {
BytesOrStr::Bytes(self.response.body())
};
let response = response.map(|()| body);
let mut s = f.debug_struct(utils::ident_str!(FatResponse));
utils::fmt_fields!(
s,
(self.response, &response),
(self.client),
(self.server),
(self.compress),
(self.future, &"[opaque Future]".as_clean()),
);
s.finish()
}
}
#[cfg(target_os = "windows")]
pub const SERVER: &str = "Kvarn/0.5.0 (Windows)";
#[cfg(target_os = "macos")]
pub const SERVER: &str = "Kvarn/0.5.0 (macOS)";
#[cfg(target_os = "linux")]
#[cfg_attr(docsrs, doc(cfg(all())))]
pub const SERVER: &str = "Kvarn/0.5.0 (Linux)";
#[cfg(target_os = "freebsd")]
pub const SERVER: &str = "Kvarn/0.5.0 (FreeBSD)";
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
target_os = "linux",
target_os = "freebsd"
)))]
pub const SERVER: &str = "Kvarn/0.5.0";