use std::collections::VecDeque;
use std::time::Duration;
use crate::Timestamp;
use crate::session::SessionParser;
use super::HttpParser;
use super::session::HttpMessage;
use super::types::{HttpConfig, HttpRequest, HttpResponse};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "kind", rename_all = "snake_case"))]
#[non_exhaustive]
pub enum HttpOutcome {
Completed,
NoResponse,
Reset,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct HttpExchange {
pub request: HttpRequest,
pub response: Option<HttpResponse>,
pub elapsed: Option<Duration>,
pub request_ts: Timestamp,
pub response_ts: Option<Timestamp>,
pub outcome: HttpOutcome,
}
impl HttpExchange {
pub fn status_class(&self) -> Option<u8> {
self.response.as_ref().and_then(|r| r.status_class())
}
pub fn is_success(&self) -> bool {
self.status_class() == Some(2)
}
pub fn is_error(&self) -> bool {
matches!(self.status_class(), Some(4) | Some(5))
}
}
#[derive(Debug, Clone)]
pub struct HttpExchangeParser {
inner: HttpParser,
pending: VecDeque<(HttpRequest, Timestamp)>,
}
impl Default for HttpExchangeParser {
fn default() -> Self {
Self::new()
}
}
impl HttpExchangeParser {
pub fn new() -> Self {
Self {
inner: HttpParser::default(),
pending: VecDeque::new(),
}
}
pub fn with_config(config: HttpConfig) -> Self {
Self {
inner: HttpParser::with_config(config),
pending: VecDeque::new(),
}
}
}
impl SessionParser for HttpExchangeParser {
type Message = HttpExchange;
fn feed_initiator(&mut self, bytes: &[u8], ts: Timestamp, _out: &mut Vec<Self::Message>) {
let mut inner = Vec::new();
self.inner.feed_initiator(bytes, ts, &mut inner);
for msg in inner {
if let HttpMessage::Request(req) = msg {
self.pending.push_back((req, ts));
}
}
}
fn feed_responder(&mut self, bytes: &[u8], ts: Timestamp, out: &mut Vec<Self::Message>) {
let mut inner = Vec::new();
self.inner.feed_responder(bytes, ts, &mut inner);
for msg in inner {
if let HttpMessage::Response(resp) = msg
&& let Some((req, req_ts)) = self.pending.pop_front()
{
let elapsed = ts.saturating_sub(req_ts);
out.push(HttpExchange {
request: req,
response: Some(resp),
elapsed: Some(elapsed),
request_ts: req_ts,
response_ts: Some(ts),
outcome: HttpOutcome::Completed,
});
}
}
}
fn fin_initiator(&mut self, _out: &mut Vec<Self::Message>) {
let mut _drop = Vec::new();
self.inner.fin_initiator(&mut _drop);
}
fn fin_responder(&mut self, out: &mut Vec<Self::Message>) {
let mut _drop = Vec::new();
self.inner.fin_responder(&mut _drop);
for (req, ts) in self.pending.drain(..) {
out.push(HttpExchange {
request: req,
response: None,
elapsed: None,
request_ts: ts,
response_ts: None,
outcome: HttpOutcome::NoResponse,
});
}
}
fn rst_initiator(&mut self) {
self.reset_pending();
}
fn rst_responder(&mut self) {
self.reset_pending();
}
fn parser_kind(&self) -> &'static str {
"http-exchange"
}
fn is_poisoned(&self) -> bool {
self.inner.is_poisoned()
}
fn poison_reason(&self) -> Option<&str> {
self.inner.poison_reason()
}
}
impl HttpExchangeParser {
fn reset_pending(&mut self) {
self.pending.clear();
}
}