pub mod http_app;
use crate::server::ShutdownWatch;
use async_trait::async_trait;
use bytes::BytesMut;
use log::{debug, error};
use std::any::Any;
use std::sync::Arc;
use std::time::Duration;
use crate::protocols::http::v2::server;
use crate::protocols::http::{ReusableHttpStream, ServerSession};
use crate::protocols::Digest;
use crate::protocols::Stream;
use crate::protocols::ALPN;
const H2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
#[async_trait]
pub trait ServerApp {
async fn process_new(
self: &Arc<Self>,
mut session: Stream,
shutdown: &ShutdownWatch,
) -> Option<Stream>;
async fn cleanup(&self) {}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default)]
pub struct HttpServerOptions {
pub h2c: bool,
pub allow_connect_method_proxying: bool,
#[doc(hidden)]
pub force_custom: bool,
pub keepalive_request_limit: Option<u32>,
pub h2_idle_timeout: Option<Duration>,
}
#[derive(Debug)]
pub struct HttpPersistentSettings {
keepalive_timeout: Option<u64>,
keepalive_reuses_remaining: Option<u32>,
user_context: Option<Box<dyn Any + Send + Sync>>,
pipelined_prefix: Option<BytesMut>,
pipelining_enabled: bool,
}
impl HttpPersistentSettings {
pub fn for_session(session: &ServerSession) -> Self {
HttpPersistentSettings {
keepalive_timeout: session.get_keepalive(),
keepalive_reuses_remaining: session.get_keepalive_reuses_remaining(),
user_context: None,
pipelined_prefix: None,
pipelining_enabled: session.pipelining_enabled(),
}
}
pub fn set_user_context(&mut self, ctx: Box<dyn Any + Send + Sync>) {
self.user_context = Some(ctx);
}
pub fn take_user_context(&mut self) -> Option<Box<dyn Any + Send + Sync>> {
self.user_context.take()
}
pub fn set_pipelined_prefix(&mut self, prefix: BytesMut) {
self.pipelined_prefix = Some(prefix);
}
pub fn apply_to_session(self, session: &mut ServerSession) {
let Self {
keepalive_timeout,
mut keepalive_reuses_remaining,
user_context,
pipelined_prefix,
pipelining_enabled,
} = self;
if let Some(reuses) = keepalive_reuses_remaining.as_mut() {
*reuses = reuses.saturating_sub(1);
}
session.set_keepalive(keepalive_timeout);
session.set_keepalive_reuses_remaining(keepalive_reuses_remaining);
session.set_connection_user_context(user_context);
session.set_pipelining_enabled(pipelining_enabled);
if let Some(prefix) = pipelined_prefix {
session.set_pipelined_prefix(prefix);
}
}
}
#[derive(Debug)]
pub struct ReusedHttpStream {
stream: Stream,
persistent_settings: Option<HttpPersistentSettings>,
}
impl ReusedHttpStream {
pub fn new(stream: Stream, persistent_settings: Option<HttpPersistentSettings>) -> Self {
ReusedHttpStream {
stream,
persistent_settings,
}
}
pub fn from_reusable_stream(
reusable: ReusableHttpStream,
mut persistent_settings: HttpPersistentSettings,
) -> Self {
let (stream, pipelined_prefix) = reusable.into_parts();
if let Some(prefix) = pipelined_prefix {
persistent_settings.set_pipelined_prefix(prefix);
}
Self::new(stream, Some(persistent_settings))
}
pub fn consume(self) -> (Stream, Option<HttpPersistentSettings>) {
(self.stream, self.persistent_settings)
}
}
#[async_trait]
pub trait HttpServerApp {
async fn process_new_http(
self: &Arc<Self>,
mut session: ServerSession,
shutdown: &ShutdownWatch,
) -> Option<ReusedHttpStream>;
fn h2_options(&self) -> Option<server::H2Options> {
None
}
fn server_options(&self) -> Option<&HttpServerOptions> {
None
}
async fn http_cleanup(&self) {}
#[doc(hidden)]
async fn process_custom_session(
self: Arc<Self>,
_stream: Stream,
_shutdown: &ShutdownWatch,
) -> Option<Stream> {
None
}
}
#[async_trait]
impl<T> ServerApp for T
where
T: HttpServerApp + Send + Sync + 'static,
{
async fn process_new(
self: &Arc<Self>,
mut stream: Stream,
shutdown: &ShutdownWatch,
) -> Option<Stream> {
let mut h2c = self.server_options().as_ref().map_or(false, |o| o.h2c);
let custom = self
.server_options()
.as_ref()
.map_or(false, |o| o.force_custom);
if stream.get_ssl_digest().is_some() {
h2c = false;
}
else if h2c && !custom {
let mut buf = [0u8; H2_PREFACE.len()];
let peeked = stream
.try_peek(&mut buf)
.await
.map_err(|e| {
debug!("Read error while peeking h2c preface {e}");
e
})
.ok()?;
if peeked {
h2c = buf == H2_PREFACE;
}
}
if h2c || matches!(stream.selected_alpn_proto(), Some(ALPN::H2)) {
let digest = Arc::new(Digest {
ssl_digest: stream.get_ssl_digest(),
timing_digest: stream.get_timing_digest(),
proxy_digest: stream.get_proxy_digest(),
socket_digest: stream.get_socket_digest(),
});
let h2_options = self.h2_options();
let h2_conn = match server::handshake(stream, h2_options).await {
Err(e) => {
error!("H2 handshake error {e}");
return None;
}
Ok(c) => c,
};
let app = self.clone();
let shutdown_for_session = shutdown.clone();
let h2_idle_timeout = self.server_options().and_then(|o| o.h2_idle_timeout);
server::accept_downstream_sessions(
h2_conn,
digest,
shutdown.clone(),
h2_idle_timeout,
|h2_stream, guard| {
let app = app.clone();
let shutdown = shutdown_for_session.clone();
pingora_runtime::current_handle().spawn(async move {
let _guard = guard;
app.process_new_http(ServerSession::new_http2(h2_stream), &shutdown)
.await;
});
},
)
.await;
} else if custom || matches!(stream.selected_alpn_proto(), Some(ALPN::Custom(_))) {
return self.clone().process_custom_session(stream, shutdown).await;
} else {
let mut session = ServerSession::new_http1(stream);
if *shutdown.borrow() {
session.set_keepalive(None);
} else {
session.set_keepalive(Some(60));
}
session.set_keepalive_reuses_remaining(
self.server_options()
.and_then(|opts| opts.keepalive_request_limit),
);
let mut result = self.process_new_http(session, shutdown).await;
while let Some((stream, persistent_settings)) = result.map(|r| r.consume()) {
let mut session = ServerSession::new_http1(stream);
if let Some(persistent_settings) = persistent_settings {
persistent_settings.apply_to_session(&mut session);
}
result = self.process_new_http(session, shutdown).await;
}
}
None
}
async fn cleanup(&self) {
self.http_cleanup().await;
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio_test::io::Builder;
#[test]
fn test_persistent_settings_user_context_roundtrip() {
let mock_io = Builder::new().build();
let mut session = ServerSession::new_http1(Box::new(mock_io));
session.set_keepalive(Some(60));
let mut settings = HttpPersistentSettings::for_session(&session);
assert!(settings.take_user_context().is_none());
settings.set_user_context(Box::new(123u64));
let mock_io2 = Builder::new().build();
let mut session2 = ServerSession::new_http1(Box::new(mock_io2));
settings.apply_to_session(&mut session2);
let ctx = session2.take_connection_user_context();
assert!(ctx.is_some());
let val = ctx.unwrap().downcast::<u64>().unwrap();
assert_eq!(*val, 123u64);
assert_eq!(session2.get_keepalive(), Some(60));
}
#[test]
fn test_persistent_settings_no_user_context_by_default() {
let mock_io = Builder::new().build();
let mut session = ServerSession::new_http1(Box::new(mock_io));
session.set_keepalive(Some(30));
let settings = HttpPersistentSettings::for_session(&session);
let mock_io2 = Builder::new().build();
let mut session2 = ServerSession::new_http1(Box::new(mock_io2));
settings.apply_to_session(&mut session2);
assert!(session2.take_connection_user_context().is_none());
assert_eq!(session2.get_keepalive(), Some(30));
}
}