use std::cell::RefCell;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use crate::application::Application;
use crate::middleware::Middleware;
use crate::request::Request;
use crate::response::Response;
use crate::server::ConnectionInfo;
pub const MAX_PER_REQUEST: usize = 4;
pub(crate) struct Sink {
deliver: Box<dyn Fn(Vec<String>) + Send + Sync>,
sent: AtomicUsize,
}
impl Sink {
pub(crate) fn new(deliver: impl Fn(Vec<String>) + Send + Sync + 'static) -> Arc<Sink> {
Arc::new(Sink { deliver: Box::new(deliver), sent: AtomicUsize::new(0) })
}
#[cfg_attr(not(feature = "http2"), allow(dead_code))]
pub(crate) fn reset(&self) {
self.sent.store(0, Ordering::Relaxed);
}
fn send(&self, links: Vec<String>) -> bool {
if self.sent.fetch_add(1, Ordering::Relaxed) >= MAX_PER_REQUEST {
return false;
}
(self.deliver)(links);
true
}
}
thread_local! {
static CURRENT: RefCell<Option<Arc<Sink>>> = const { RefCell::new(None) };
}
#[cfg(feature = "http2")]
tokio::task_local! {
static TASK: Option<Arc<Sink>>;
}
pub(crate) fn with_sink<R>(sink: Option<Arc<Sink>>, f: impl FnOnce() -> R) -> R {
struct Restore(Option<Arc<Sink>>);
impl Drop for Restore {
fn drop(&mut self) {
let previous = self.0.take();
CURRENT.with(|c| *c.borrow_mut() = previous);
}
}
let previous = CURRENT.with(|c| std::mem::replace(&mut *c.borrow_mut(), sink));
let _restore = Restore(previous);
f()
}
#[cfg(feature = "http2")]
pub(crate) async fn scope<F: std::future::Future>(sink: Option<Arc<Sink>>, future: F) -> F::Output {
TASK.scope(sink, future).await
}
fn current() -> Option<Arc<Sink>> {
#[cfg(feature = "http2")]
if let Ok(Some(sink)) = TASK.try_with(|s| s.clone()) {
return Some(sink);
}
CURRENT.with(|c| c.borrow().clone())
}
pub fn available() -> bool {
current().is_some()
}
pub fn send<S: AsRef<str>>(links: &[S]) -> bool {
send_from(Source::Handler, links)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Source {
Handler,
Configured,
Upstream,
}
impl Source {
fn label(self) -> &'static str {
match self {
Source::Handler => "handler",
Source::Configured => "configured",
Source::Upstream => "upstream",
}
}
}
static SENT: [AtomicUsize; 3] = [AtomicUsize::new(0), AtomicUsize::new(0), AtomicUsize::new(0)];
pub(crate) fn send_from<S: AsRef<str>>(source: Source, links: &[S]) -> bool {
if links.is_empty() || !links.iter().all(|l| valid(l.as_ref())) {
return false;
}
let Some(sink) = current() else { return false };
let sent = sink.send(links.iter().map(|l| l.as_ref().to_string()).collect());
if sent {
SENT[source as usize].fetch_add(1, Ordering::Relaxed);
}
sent
}
pub(crate) fn upstream_links(head: &[u8]) -> Vec<String> {
let text = String::from_utf8_lossy(head);
let mut lines = text.split("\r\n");
let is_103 = lines.next().and_then(|l| l.split(' ').nth(1)) == Some("103");
if !is_103 {
return Vec::new();
}
lines
.filter_map(|line| line.split_once(':'))
.filter(|(name, _)| name.trim().eq_ignore_ascii_case("link"))
.map(|(_, value)| value.trim().to_string())
.filter(|value| valid(value))
.collect()
}
pub(crate) fn forward_upstream(head: &[u8]) {
let links = upstream_links(head);
if !links.is_empty() {
send_from(Source::Upstream, &links);
}
}
pub(crate) fn prometheus_text() -> String {
let counts: Vec<usize> = SENT.iter().map(|c| c.load(Ordering::Relaxed)).collect();
if counts.iter().all(|n| *n == 0) {
return String::new();
}
let mut out = String::from("# HELP rws_early_hints_total 103 Early Hints responses sent, by where the hints came from\n# TYPE rws_early_hints_total counter\n");
for source in [Source::Handler, Source::Configured, Source::Upstream] {
out.push_str(&format!("rws_early_hints_total{{source=\"{}\"}} {}\n", source.label(), counts[source as usize]));
}
out
}
pub fn valid(link: &str) -> bool {
!link.trim().is_empty() && !link.bytes().any(|b| matches!(b, b'\r' | b'\n' | 0))
}
pub(crate) fn http1_wanted(request: &Request) -> bool {
request.http_version == "HTTP/1.1" && crate::server_config::ServerConfig::current().early_hints_http1
}
pub(crate) fn http1_head(links: &[String]) -> Vec<u8> {
let mut head = b"HTTP/1.1 103 Early Hints\r\n".to_vec();
for link in links {
head.extend_from_slice(b"Link: ");
head.extend_from_slice(link.as_bytes());
head.extend_from_slice(b"\r\n");
}
head.extend_from_slice(b"\r\n");
head
}
#[derive(Clone, Debug)]
pub struct EarlyHintsLayer {
links: Arc<Vec<String>>,
}
impl EarlyHintsLayer {
pub fn new<I, S>(links: I) -> EarlyHintsLayer
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
EarlyHintsLayer { links: Arc::new(links.into_iter().map(Into::into).filter(|l: &String| valid(l)).collect()) }
}
pub fn links(&self) -> &[String] {
&self.links
}
}
impl Middleware for EarlyHintsLayer {
fn handle(&self, request: &Request, connection: &ConnectionInfo, next: &dyn Application) -> Result<Response, String> {
if !self.links.is_empty() {
send_from(Source::Configured, &self.links);
}
next.execute(request, connection)
}
fn runs_inline(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
true
}
fn accepts_streamed_body(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
true
}
}
#[cfg(test)]
mod tests;