use bytes::Bytes;
use flowscope::FlowSide;
use flowscope::http::{HttpEvent, HttpProxyParser, SwitchKind};
fn client_traffic() -> Vec<&'static [u8]> {
vec![
b"POST /orders HTTP/1.1\r\nHost: api.exam",
b"ple.com\r\nContent-Length: 11\r\n\r\nhello",
b" world",
b"GET /health HTTP/1.1\r\nHost: api.example.com\r\n\r\n",
]
}
fn main() {
println!("== a well-framed connection ==\n");
let mut proxy = HttpProxyParser::new();
let mut forwarded: Vec<u8> = Vec::new();
let mut body_seen = 0usize;
for slice in client_traffic() {
let mut pending = Bytes::from_static(slice);
while !pending.is_empty() {
let accepted = proxy.push(FlowSide::Initiator, &pending);
if accepted == 0 {
if proxy.is_poisoned() || proxy.is_tunnelled() {
break;
}
if proxy.next_event().is_none() {
break;
}
continue;
}
pending = pending.slice(accepted..);
while let Some(ev) = proxy.next_event() {
match ev {
HttpEvent::RequestHead(head) => {
let authority = head
.authority()
.map(|a| a.host)
.unwrap_or_else(|_| "<unroutable>".into());
println!(
"route {} {} -> backend for {authority} ({:?})",
head.method_str().unwrap_or("?"),
head.path_str().unwrap_or("?"),
head.framing,
);
forwarded.extend_from_slice(&head.raw);
}
HttpEvent::Body { data, raw, .. } => {
body_seen += data.len();
forwarded.extend_from_slice(&raw);
}
HttpEvent::Trailers { raw, .. } => forwarded.extend_from_slice(&raw),
HttpEvent::End { .. } => println!(" message complete"),
HttpEvent::SwitchProtocols { kind } => {
println!(" protocol switch: {kind:?} — splicing from here");
if matches!(kind, SwitchKind::ConnectTunnel) {
break;
}
}
_ => {}
}
}
}
}
let original: Vec<u8> = client_traffic().concat();
println!(
"\nforwarded {} bytes, body payload {body_seen} bytes",
forwarded.len()
);
assert_eq!(
forwarded, original,
"raw spans must reproduce the connection exactly"
);
println!("forwarded bytes are identical to what arrived ✓");
println!("\n== an ambiguously framed connection ==\n");
let mut proxy = HttpProxyParser::new();
let smuggled = Bytes::from_static(
b"POST /orders HTTP/1.1\r\nHost: api.example.com\r\n\
Content-Length: 6\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\nGET /admin HTTP/1.1\r\n\r\n",
);
proxy.push(FlowSide::Initiator, &smuggled);
while proxy.next_event().is_some() {}
match proxy.poison() {
Some(reason) => {
println!("refused: {reason}");
println!(
" (client fault: {:?})",
reason.implies_client_fault()
);
let more = Bytes::from_static(b"GET /admin HTTP/1.1\r\n\r\n");
assert_eq!(proxy.push(FlowSide::Initiator, &more), 0);
println!(" connection is closed to further bytes ✓");
}
None => unreachable!("this framing is ambiguous by construction"),
}
}