use std::collections::VecDeque;
use std::io::{self, Read, Write};
use std::net::TcpStream;
use std::sync::Arc;
use std::time::Duration;
use bytes::Bytes;
use foundation_core::valtron::{Ctx,
self, Pipe, PipeReceiver, PipeSender, TaskIterator, TaskStatus,
TryRecvError,
};
use foundation_netio::shared::http::{
Proto, RequestDescriptor, SimpleHeader, SimpleHeaders, Status,
};
use foundation_netio::http2::channel::{H2Channel, H2StreamEvent};
use foundation_netio::http2::connection::H2Request;
use crate::shared::transport::{
body_stream_from_pipe, head_stream_from_pipe, BodyStream, HeadStream,
Transport, TransportCapabilities, TransportError, TransportStream,
};
const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
const POLL_DELAY: Duration = Duration::from_millis(10);
enum H2Socket {
Tcp(TcpStream),
#[cfg(unix)]
Unix(std::os::unix::net::UnixStream),
}
impl H2Socket {
fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
match self {
Self::Tcp(s) => s.set_nonblocking(nonblocking),
#[cfg(unix)]
Self::Unix(s) => s.set_nonblocking(nonblocking),
}
}
}
impl Read for H2Socket {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
Self::Tcp(s) => s.read(buf),
#[cfg(unix)]
Self::Unix(s) => s.read(buf),
}
}
}
impl Write for H2Socket {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
Self::Tcp(s) => s.write(buf),
#[cfg(unix)]
Self::Unix(s) => s.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
Self::Tcp(s) => s.flush(),
#[cfg(unix)]
Self::Unix(s) => s.flush(),
}
}
}
#[derive(Clone, Default)]
pub struct H2Transport {
#[cfg(unix)]
unix_path: Option<std::path::PathBuf>,
}
impl H2Transport {
#[must_use]
pub fn new() -> Self { Self::default() }
#[cfg(unix)]
#[must_use]
pub fn unix(path: impl Into<std::path::PathBuf>) -> Self {
Self { unix_path: Some(path.into()) }
}
}
impl Transport for H2Transport {
fn capabilities(&self) -> TransportCapabilities {
TransportCapabilities {
request_streaming: true,
full_duplex: true,
h2_trailers: true,
http_versions: &[Proto::HTTP20],
multiplexed: false,
}
}
fn open(&self, request: RequestDescriptor) -> Result<TransportStream, TransportError> {
let host = request.request_uri.host_str()
.map(|s| s.to_string())
.unwrap_or_else(|| "localhost".into());
let port = request.request_uri.port_or_default();
let path = request.request_uri.path().to_string();
let method = request.method.clone();
let req_headers = request.headers.clone();
#[cfg(unix)]
let mut stream = match &self.unix_path {
Some(path) => H2Socket::Unix(
std::os::unix::net::UnixStream::connect(path)
.map_err(|e| TransportError::Connect(Arc::new(e)))?,
),
None => {
let addr = format!("{host}:{port}");
H2Socket::Tcp(
TcpStream::connect_timeout(
&addr.parse().map_err(|e| TransportError::Connect(Arc::new(e)))?,
HANDSHAKE_TIMEOUT,
)
.map_err(|e| TransportError::Connect(Arc::new(e)))?,
)
}
};
#[cfg(not(unix))]
let mut stream = {
let addr = format!("{host}:{port}");
H2Socket::Tcp(
TcpStream::connect_timeout(
&addr.parse().map_err(|e| TransportError::Connect(Arc::new(e)))?,
HANDSHAKE_TIMEOUT,
)
.map_err(|e| TransportError::Connect(Arc::new(e)))?,
)
};
let mut channel = H2Channel::new(false); loop {
match channel.client_handshake_step() {
Ok(()) => break, Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
let out = channel.drain_output();
if !out.is_empty() {
stream.write_all(&out).map_err(|e| TransportError::Connect(Arc::new(e)))?;
}
let mut buf = [0u8; 8192];
match stream.read(&mut buf) {
Ok(0) => return Err(TransportError::Connect(Arc::new(io::Error::new(
io::ErrorKind::UnexpectedEof, "connection closed during handshake"
)))),
Ok(n) => channel.feed_input(&buf[..n]),
Err(e) => return Err(TransportError::Connect(Arc::new(e))),
}
}
Err(e) => return Err(TransportError::Connect(Arc::new(e))),
}
}
let out = channel.drain_output();
if !out.is_empty() {
stream.write_all(&out).map_err(|e| TransportError::Connect(Arc::new(e)))?;
}
stream.flush().ok();
let (send_tx, send_rx) = Pipe::<Bytes>::new();
let (head_tx, head_rx) = Pipe::<(Status, SimpleHeaders)>::new();
let (body_tx, body_rx) = Pipe::<Bytes>::new();
let (trailer_tx, trailer_rx) = Pipe::<SimpleHeaders>::with_depth(1);
let pump = H2Pump {
stream,
channel,
state: PumpPhase::SendingRequest,
send_rx,
head_tx,
body_tx,
trailer_tx,
method: method.to_string(),
host,
port,
path,
req_headers,
request_sent: false,
response_head_sent: false,
stream_id: 0,
body_complete: false,
request_ended: false,
pending_body: VecDeque::new(),
response_ended: false,
};
valtron::send(pump).map_err(|e| {
TransportError::Connect(Arc::new(io::Error::other(e.to_string())))
})?;
let head: HeadStream = head_stream_from_pipe(head_rx);
let recv_body: BodyStream = body_stream_from_pipe(body_rx);
Ok(TransportStream { send_body: Arc::new(send_tx), head, recv_body, trailers: trailer_rx })
}
}
enum PumpPhase {
SendingRequest,
WaitingResponse,
Draining,
Done,
}
struct H2Pump {
stream: H2Socket,
channel: H2Channel,
state: PumpPhase,
send_rx: PipeReceiver<Bytes>,
head_tx: PipeSender<(Status, SimpleHeaders)>,
body_tx: PipeSender<Bytes>,
trailer_tx: PipeSender<SimpleHeaders>,
method: String,
host: String,
port: u16,
path: String,
req_headers: SimpleHeaders,
request_sent: bool,
response_head_sent: bool,
stream_id: u32,
body_complete: bool,
request_ended: bool,
pending_body: VecDeque<Bytes>,
response_ended: bool,
}
impl H2Pump {
fn pump_request_body(&mut self) -> bool {
if !self.request_sent || self.request_ended {
return true;
}
loop {
match self.send_rx.try_recv() {
Ok(chunk) => {
if !chunk.is_empty()
&& self
.channel
.send_data_frame(self.stream_id, &chunk, false)
.is_err()
{
return false;
}
}
Err(TryRecvError::Closed) => {
self.body_complete = true;
break;
}
Err(TryRecvError::Empty) => break,
}
}
if self.body_complete && !self.request_ended {
if self
.channel
.send_data_frame(self.stream_id, &[], true)
.is_err()
{
return false;
}
self.request_ended = true;
}
true
}
fn flush_response_body(&mut self) -> bool {
while let Some(chunk) = self.pending_body.front() {
match self.body_tx.try_send(chunk.clone()) {
Ok(()) => {
self.pending_body.pop_front();
}
Err(e) if e.is_full() => return true,
Err(_) => return false,
}
}
if self.response_ended {
self.body_tx.close();
}
true
}
}
impl TaskIterator for H2Pump {
type Ready = ();
type Pending = ();
fn next_status(&mut self, _ctx: Ctx) -> Option<TaskStatus<(), ()>> {
self.stream.set_nonblocking(true).ok();
loop {
let mut buf = [0u8; 8192];
match self.stream.read(&mut buf) {
Ok(0) => {
tracing::debug!(
phase = match self.state {
PumpPhase::SendingRequest => "SendingRequest",
PumpPhase::WaitingResponse => "WaitingResponse",
PumpPhase::Draining => "Draining",
PumpPhase::Done => "Done",
},
"h2 pump: connection closed by peer"
);
self.head_tx.close();
self.body_tx.close();
return None;
}
Ok(n) => self.channel.feed_input(&buf[..n]),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
}
Err(_e) => {
tracing::warn!(err = %_e, "h2 pump: socket read error — exiting");
self.head_tx.close();
self.body_tx.close();
return None;
}
}
match self.state {
PumpPhase::SendingRequest => {
let mut chunks: Vec<Bytes> = Vec::new();
loop {
match self.send_rx.try_recv() {
Ok(b) => chunks.push(b),
Err(TryRecvError::Closed) => {
self.body_complete = true;
break;
}
Err(TryRecvError::Empty) => break,
}
}
if !self.request_sent {
let no_body = self.body_complete && chunks.is_empty();
let authority = Bytes::from(format!("{}:{}", self.host, self.port));
let mut h2_headers = Vec::new();
for (k, vals) in &self.req_headers {
for v in vals {
h2_headers.push((
Bytes::copy_from_slice(k.to_string().as_bytes()),
Bytes::copy_from_slice(v.as_bytes()),
));
}
}
let req = H2Request {
method: Bytes::copy_from_slice(self.method.to_uppercase().as_bytes()),
scheme: Bytes::from_static(b"http"),
authority,
path: Bytes::copy_from_slice(self.path.as_bytes()),
headers: h2_headers,
body: None,
end_stream: no_body,
};
match self.channel.send_request(&req) {
Ok(sid) => {
self.stream_id = sid;
self.request_sent = true;
self.request_ended = no_body;
}
Err(_e) => {
let _ = self.head_tx.try_send((Status::BadGateway, SimpleHeaders::new()));
self.head_tx.close();
self.body_tx.close();
return None;
}
}
}
if self.request_sent && !self.request_ended {
for chunk in chunks {
if chunk.is_empty() {
continue;
}
if self
.channel
.send_data_frame(self.stream_id, &chunk, false)
.is_err()
{
self.head_tx.close();
self.body_tx.close();
return None;
}
}
if self.body_complete {
if self
.channel
.send_data_frame(self.stream_id, &[], true)
.is_err()
{
self.head_tx.close();
self.body_tx.close();
return None;
}
self.request_ended = true;
}
}
let out = self.channel.drain_output();
if !out.is_empty() {
match self.stream.write(&out) {
Ok(_) => {
self.stream.flush().ok();
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
return Some(TaskStatus::Delayed(POLL_DELAY));
}
Err(_e) => {
let _ = self.head_tx.try_send((Status::BadGateway, SimpleHeaders::new()));
self.head_tx.close();
self.body_tx.close();
return None;
}
}
}
if self.request_sent {
self.state = PumpPhase::WaitingResponse;
} else {
return Some(TaskStatus::Delayed(POLL_DELAY));
}
}
PumpPhase::WaitingResponse => {
if !self.pump_request_body() {
self.head_tx.close();
self.body_tx.close();
return None;
}
match self.channel.recv_response() {
Ok(Some((_sid, resp))) => {
let mut status = Status::OK;
let mut resp_headers = SimpleHeaders::new();
for (name, value) in &resp.headers {
let n = String::from_utf8_lossy(name);
if n == ":status" {
if let Ok(code) = String::from_utf8_lossy(value).trim().parse::<u16>() {
status = status_from_u16(code);
}
} else {
resp_headers.entry(SimpleHeader::from(n.to_string()))
.or_default()
.push(String::from_utf8_lossy(value).to_string());
}
}
let _ = self.head_tx.try_send((status, resp_headers));
self.head_tx.close();
self.response_head_sent = true;
if let Some(body) = &resp.body {
if !body.is_empty() {
self.pending_body.push_back(body.clone());
}
}
if resp.end_stream {
self.response_ended = true;
}
if !self.flush_response_body() {
return None;
}
if self.response_ended && self.pending_body.is_empty() {
self.state = PumpPhase::Done;
return Some(TaskStatus::Pending(()));
}
self.state = PumpPhase::Draining;
}
Ok(None) => {
let _ = self.head_tx.try_send((Status::BadGateway, SimpleHeaders::new()));
self.head_tx.close();
self.body_tx.close();
return None;
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
let out = self.channel.drain_output();
if !out.is_empty() {
match self.stream.write(&out) {
Ok(_) => { self.stream.flush().ok(); }
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
Err(_e) => {
self.head_tx.close();
self.body_tx.close();
return None;
}
}
}
return Some(TaskStatus::Delayed(POLL_DELAY));
}
Err(_e) => {
let _ = self.head_tx.try_send((Status::BadGateway, SimpleHeaders::new()));
self.head_tx.close();
self.body_tx.close();
return None;
}
}
}
PumpPhase::Draining => {
if !self.pump_request_body() {
self.body_tx.close();
return None;
}
if !self.flush_response_body() {
return None;
}
if !self.pending_body.is_empty() {
return Some(TaskStatus::Delayed(POLL_DELAY));
}
if self.response_ended {
self.state = PumpPhase::Done;
return Some(TaskStatus::Pending(()));
}
match self.channel.recv_stream_event() {
Ok(Some((_sid, H2StreamEvent::Data { data, end_stream: end, .. }))) => {
if !data.is_empty() {
self.pending_body.push_back(data);
}
if end {
self.response_ended = true;
}
if !self.flush_response_body() {
return None;
}
if self.response_ended && self.pending_body.is_empty() {
self.state = PumpPhase::Done;
return Some(TaskStatus::Pending(()));
}
}
Ok(Some((_sid, H2StreamEvent::Trailers { headers, .. }))) => {
let mut trailers = SimpleHeaders::new();
for (name, value) in &headers {
let n = String::from_utf8_lossy(name);
let v = String::from_utf8_lossy(value);
if !n.starts_with(':') {
trailers
.entry(SimpleHeader::from(n.to_string()))
.or_default()
.push(v.to_string());
}
}
let _ = self.trailer_tx.try_send(trailers);
self.trailer_tx.close();
self.response_ended = true;
if !self.flush_response_body() {
return None;
}
if self.pending_body.is_empty() {
self.state = PumpPhase::Done;
return Some(TaskStatus::Pending(()));
}
}
Ok(None) => {
self.body_tx.close();
self.trailer_tx.close();
self.state = PumpPhase::Done;
return Some(TaskStatus::Pending(()));
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
let out = self.channel.drain_output();
if !out.is_empty() {
let _ = self.stream.write(&out);
self.stream.flush().ok();
}
return Some(TaskStatus::Delayed(POLL_DELAY));
}
Err(_e) => {
self.body_tx.close();
self.trailer_tx.close();
return None;
}
}
}
PumpPhase::Done => {
self.body_tx.close();
self.head_tx.close();
self.trailer_tx.close();
return None;
}
}
let out = self.channel.drain_output();
if !out.is_empty() {
match self.stream.write(&out) {
Ok(_) => { self.stream.flush().ok(); }
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
return Some(TaskStatus::Delayed(POLL_DELAY));
}
Err(_e) => {
self.head_tx.close();
self.body_tx.close();
return None;
}
}
}
}
}
}
fn status_from_u16(code: u16) -> Status {
match code {
200 => Status::OK,
201 => Status::Created,
204 => Status::NoContent,
400 => Status::BadRequest,
401 => Status::Unauthorized,
403 => Status::Forbidden,
404 => Status::NotFound,
500 => Status::InternalServerError,
502 => Status::BadGateway,
503 => Status::ServiceUnavailable,
_ => Status::InternalServerError,
}
}