use bytes::Bytes;
use pingora_error::Result;
use pingora_http::{RequestHeader, ResponseHeader};
use std::time::Duration;
use super::v2::client::Http2Session;
use super::{custom::client::Session, v1::client::HttpSession as Http1Session};
use crate::protocols::{Digest, SocketAddr, Stream};
pub enum HttpSession<S = ()> {
H1(Http1Session),
H2(Http2Session),
Custom(S),
}
impl<S: Session> HttpSession<S> {
pub fn as_http1(&self) -> Option<&Http1Session> {
match self {
Self::H1(s) => Some(s),
Self::H2(_) => None,
Self::Custom(_) => None,
}
}
pub fn as_http2(&self) -> Option<&Http2Session> {
match self {
Self::H1(_) => None,
Self::H2(s) => Some(s),
Self::Custom(_) => None,
}
}
pub fn as_custom(&self) -> Option<&S> {
match self {
Self::H1(_) => None,
Self::H2(_) => None,
Self::Custom(c) => Some(c),
}
}
pub fn as_custom_mut(&mut self) -> Option<&mut S> {
match self {
Self::H1(_) => None,
Self::H2(_) => None,
Self::Custom(c) => Some(c),
}
}
pub async fn write_request_header(&mut self, req: Box<RequestHeader>) -> Result<()> {
match self {
HttpSession::H1(h1) => {
h1.write_request_header(req).await?;
Ok(())
}
HttpSession::H2(h2) => h2.write_request_header(req, false),
HttpSession::Custom(c) => c.write_request_header(req, false).await,
}
}
pub async fn write_request_body(&mut self, data: Bytes, end: bool) -> Result<()> {
match self {
HttpSession::H1(h1) => {
h1.write_body(&data).await?;
Ok(())
}
HttpSession::H2(h2) => h2.write_request_body(data, end).await,
HttpSession::Custom(c) => c.write_request_body(data, end).await,
}
}
pub async fn finish_request_body(&mut self) -> Result<()> {
match self {
HttpSession::H1(h1) => {
h1.finish_body().await?;
Ok(())
}
HttpSession::H2(h2) => h2.finish_request_body(),
HttpSession::Custom(c) => c.finish_request_body().await,
}
}
pub fn set_read_timeout(&mut self, timeout: Option<Duration>) {
match self {
HttpSession::H1(h1) => h1.read_timeout = timeout,
HttpSession::H2(h2) => h2.read_timeout = timeout,
HttpSession::Custom(c) => c.set_read_timeout(timeout),
}
}
pub fn set_write_timeout(&mut self, timeout: Option<Duration>) {
match self {
HttpSession::H1(h1) => h1.write_timeout = timeout,
HttpSession::H2(h2) => h2.write_timeout = timeout,
HttpSession::Custom(c) => c.set_write_timeout(timeout),
}
}
pub async fn read_response_header(&mut self) -> Result<()> {
match self {
HttpSession::H1(h1) => {
h1.read_response().await?;
Ok(())
}
HttpSession::H2(h2) => h2.read_response_header().await,
HttpSession::Custom(c) => c.read_response_header().await,
}
}
pub async fn read_response_body(&mut self) -> Result<Option<Bytes>> {
match self {
HttpSession::H1(h1) => h1.read_body_bytes().await,
HttpSession::H2(h2) => h2.read_response_body().await,
HttpSession::Custom(c) => c.read_response_body().await,
}
}
pub fn response_done(&mut self) -> bool {
match self {
HttpSession::H1(h1) => h1.is_body_done(),
HttpSession::H2(h2) => h2.response_finished(),
HttpSession::Custom(c) => c.response_finished(),
}
}
pub async fn shutdown(&mut self) {
match self {
Self::H1(s) => s.shutdown().await,
Self::H2(s) => s.shutdown(),
Self::Custom(c) => c.abandon("shutdown").await,
}
}
pub fn response_header(&self) -> Option<&ResponseHeader> {
match self {
Self::H1(s) => s.resp_header(),
Self::H2(s) => s.response_header(),
Self::Custom(c) => c.response_header(),
}
}
pub fn digest(&self) -> Option<&Digest> {
match self {
Self::H1(s) => Some(s.digest()),
Self::H2(s) => s.digest(),
Self::Custom(c) => c.digest(),
}
}
pub fn digest_mut(&mut self) -> Option<&mut Digest> {
match self {
Self::H1(s) => Some(s.digest_mut()),
Self::H2(s) => s.digest_mut(),
Self::Custom(s) => s.digest_mut(),
}
}
pub fn server_addr(&self) -> Option<&SocketAddr> {
match self {
Self::H1(s) => s.server_addr(),
Self::H2(s) => s.server_addr(),
Self::Custom(s) => s.server_addr(),
}
}
pub fn client_addr(&self) -> Option<&SocketAddr> {
match self {
Self::H1(s) => s.client_addr(),
Self::H2(s) => s.client_addr(),
Self::Custom(s) => s.client_addr(),
}
}
pub fn stream(&self) -> Option<&Stream> {
match self {
Self::H1(s) => Some(s.stream()),
Self::H2(_) => None,
Self::Custom(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::{HttpSession, Session};
use crate::protocols::http::custom::{BodyWrite, CustomMessageWrite};
use crate::protocols::{Digest, SocketAddr, UniqueIDType};
use async_trait::async_trait;
use bytes::Bytes;
use futures::Stream;
use http::HeaderMap;
use pingora_error::Result;
use pingora_http::{RequestHeader, ResponseHeader};
use std::sync::{Arc, Mutex};
use std::time::Duration;
struct ShutdownRecordingCustom {
shutdown_calls: Arc<Mutex<Vec<String>>>,
}
#[async_trait]
impl Session for ShutdownRecordingCustom {
async fn shutdown(&mut self, code: u32, ctx: &str) {
self.shutdown_calls
.lock()
.unwrap()
.push(format!("shutdown({code}, {ctx})"));
}
async fn abandon(&mut self, ctx: &str) {
self.shutdown_calls
.lock()
.unwrap()
.push(format!("abandon({ctx})"));
}
async fn write_request_header(
&mut self,
_req: Box<RequestHeader>,
_end: bool,
) -> Result<()> {
unreachable!("not used by the shutdown dispatch test")
}
async fn write_request_body(&mut self, _data: Bytes, _end: bool) -> Result<()> {
unreachable!("not used by the shutdown dispatch test")
}
async fn finish_request_body(&mut self) -> Result<()> {
unreachable!("not used by the shutdown dispatch test")
}
fn set_read_timeout(&mut self, _timeout: Option<Duration>) {
unreachable!("not used by the shutdown dispatch test")
}
fn set_write_timeout(&mut self, _timeout: Option<Duration>) {
unreachable!("not used by the shutdown dispatch test")
}
async fn read_response_header(&mut self) -> Result<()> {
unreachable!("not used by the shutdown dispatch test")
}
async fn read_response_body(&mut self) -> Result<Option<Bytes>> {
unreachable!("not used by the shutdown dispatch test")
}
fn response_finished(&self) -> bool {
unreachable!("not used by the shutdown dispatch test")
}
fn response_header(&self) -> Option<&ResponseHeader> {
unreachable!("not used by the shutdown dispatch test")
}
fn was_upgraded(&self) -> bool {
unreachable!("not used by the shutdown dispatch test")
}
fn digest(&self) -> Option<&Digest> {
unreachable!("not used by the shutdown dispatch test")
}
fn digest_mut(&mut self) -> Option<&mut Digest> {
unreachable!("not used by the shutdown dispatch test")
}
fn server_addr(&self) -> Option<&SocketAddr> {
unreachable!("not used by the shutdown dispatch test")
}
fn client_addr(&self) -> Option<&SocketAddr> {
unreachable!("not used by the shutdown dispatch test")
}
async fn read_trailers(&mut self) -> Result<Option<HeaderMap>> {
unreachable!("not used by the shutdown dispatch test")
}
fn fd(&self) -> UniqueIDType {
unreachable!("not used by the shutdown dispatch test")
}
async fn check_response_end_or_error(&mut self, _headers: bool) -> Result<bool> {
unreachable!("not used by the shutdown dispatch test")
}
fn take_request_body_writer(&mut self) -> Option<Box<dyn BodyWrite>> {
unreachable!("not used by the shutdown dispatch test")
}
async fn finish_custom(&mut self) -> Result<()> {
unreachable!("not used by the shutdown dispatch test")
}
fn take_custom_message_reader(
&mut self,
) -> Option<Box<dyn Stream<Item = Result<Bytes>> + Unpin + Send + Sync + 'static>> {
unreachable!("not used by the shutdown dispatch test")
}
async fn drain_custom_messages(&mut self) -> Result<()> {
unreachable!("not used by the shutdown dispatch test")
}
fn take_custom_message_writer(&mut self) -> Option<Box<dyn CustomMessageWrite>> {
unreachable!("not used by the shutdown dispatch test")
}
}
#[tokio::test]
async fn custom_session_shutdown_signals_an_incomplete_message() {
let shutdown_calls = Arc::new(Mutex::new(Vec::new()));
let mut session = HttpSession::Custom(ShutdownRecordingCustom {
shutdown_calls: shutdown_calls.clone(),
});
session.shutdown().await;
assert_eq!(*shutdown_calls.lock().unwrap(), ["abandon(shutdown)"]);
}
}