use std::convert::Infallible;
use std::sync::Arc;
use bytes::Bytes;
use futures::{SinkExt, StreamExt};
use crate::json_frame::{
decode_json_frame, encode_json_frame, json_frame_to_proto, json_open_to_subscribe,
proto_frame_to_json,
};
use crate::pb::{frame::Kind, Frame, Header, Message as WsMessage, Reset, Trailer};
use crate::{decode_frame, encode_frame, grpc_frame, metadata, Deframer, WsBinding};
use http::uri::PathAndQuery;
use http::{HeaderMap, Request};
use http_body_util::{BodyExt, StreamBody};
use hyper::body::Frame as BodyFrame;
use hyper_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
use hyper_tungstenite::tungstenite::protocol::CloseFrame;
use hyper_tungstenite::tungstenite::Message as TungMessage;
use hyper_tungstenite::HyperWebsocket;
use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;
use tonic::body::Body as TonicBody;
use tonic::metadata::MetadataMap;
use tonic::{Code, Status};
use crate::{Runtime, CT_GRPC, DEADLINE_GRACE};
struct StreamState {
req_tx: Option<mpsc::Sender<Bytes>>,
task: tokio::task::JoinHandle<()>,
}
pub(crate) struct WsParams {
pub codec: Option<bool>,
pub method: Option<String>,
pub annotation: Option<Arc<WsBinding>>,
}
pub(crate) async fn serve(rt: Runtime, websocket: HyperWebsocket, reject: Option<Status>, params: WsParams) {
let keepalive = rt.cfg.ws_keepalive;
let max_silence = keepalive.map(|iv| iv + rt.cfg.ws_keepalive_timeout);
let ws = match websocket.await {
Ok(ws) => ws,
Err(e) => {
tracing::debug!("ws upgrade failed: {e}");
return;
}
};
let (mut ws_sink, mut ws_stream) = ws.split();
if let Some(status) = reject {
let _ = ws_sink.send(close_for_status(&status)).await;
let _ = ws_sink.close().await;
return;
}
let (outbound_tx, mut outbound_rx) = mpsc::channel::<TungMessage>(64);
let writer = tokio::spawn(async move {
let mut ping = keepalive.map(keepalive_interval);
loop {
tokio::select! {
msg = outbound_rx.recv() => {
let Some(msg) = msg else { break };
if ws_sink.send(msg).await.is_err() { break; }
}
_ = next_tick(ping.as_mut()) => {
if ws_sink.send(TungMessage::Ping(Bytes::new())).await.is_err() { break; }
}
}
}
});
let annotation = params.annotation;
let method_url = params.method.unwrap_or_default();
let mut codec: Option<bool> = params.codec;
let mut opened = false;
let mut stream: Option<StreamState> = None;
let mut deadline = max_silence.map(|d| tokio::time::Instant::now() + d);
loop {
tokio::select! {
_ = sleep_until(deadline) => {
tracing::debug!("ws keepalive: no pong within timeout; dropping connection");
break;
}
incoming = ws_stream.next() => {
let Some(incoming) = incoming else { break };
let msg = match incoming { Ok(m) => m, Err(_) => break };
if let Some(d) = max_silence {
deadline = Some(tokio::time::Instant::now() + d);
}
let decoded = match msg {
TungMessage::Binary(data) => {
if *codec.get_or_insert(false) { continue } decode_binary(&data, &method_url, &mut opened).map(|f| (f, false))
}
TungMessage::Text(text) => {
if !*codec.get_or_insert(true) { continue } decode_text(&text, &method_url, &mut opened).map(|f| (f, true))
}
TungMessage::Close(_) => break,
_ => continue,
};
let Some((frame, json)) = decoded else { continue };
handle_frame(&rt, frame, json, &annotation, &outbound_tx, &mut stream).await;
}
}
}
if let Some(state) = stream {
state.task.abort();
}
drop(outbound_tx);
let _ = writer.await;
}
async fn handle_frame(
rt: &Runtime,
frame: Frame,
json: bool,
annotation: &Option<Arc<WsBinding>>,
outbound_tx: &mpsc::Sender<TungMessage>,
stream: &mut Option<StreamState>,
) {
match frame.kind {
Some(Kind::Subscribe(sub)) => {
if stream.is_some() {
send_reset(outbound_tx, json, Code::InvalidArgument, "stream already open").await;
return;
}
if sub.initial_payload.len() > rt.cfg.max_message_bytes {
send_reset(outbound_tx, json, Code::ResourceExhausted, "request message exceeds size limit").await;
return;
}
let path: PathAndQuery = match sub.method.parse() {
Ok(p) => p,
Err(_) => {
send_reset(outbound_tx, json, Code::InvalidArgument, "invalid method").await;
return;
}
};
let md = metadata::metadata_vec_to_metadata(&sub.headers);
let (req_tx, req_rx) = mpsc::channel::<Bytes>(16);
if !sub.initial_payload.is_empty() {
let _ = req_tx.send(sub.initial_payload).await;
}
let mut req_tx_state = Some(req_tx.clone());
if let Some(ann) = annotation {
if !ann.has_body() {
let _ = req_tx.send(Bytes::new()).await;
req_tx_state = None;
}
}
let headers = md.into_headers();
let timeout = metadata::grpc_timeout_from_millis(sub.timeout_millis);
let task = tokio::spawn(run_stream(
rt.clone(), path, headers, timeout, req_rx, outbound_tx.clone(), json, annotation.clone(),
));
*stream = Some(StreamState { req_tx: req_tx_state, task });
}
Some(Kind::Message(msg)) => {
if msg.payload.len() > rt.cfg.max_message_bytes {
send_reset(outbound_tx, json, Code::ResourceExhausted, "request message exceeds size limit").await;
if let Some(state) = stream.take() {
state.task.abort();
}
return;
}
if let Some(state) = stream {
if let Some(tx) = &state.req_tx {
let _ = tx.send(msg.payload).await;
}
}
}
Some(Kind::HalfClose(_)) => {
if let Some(state) = stream {
state.req_tx = None;
}
}
Some(Kind::Reset(_)) => {
if let Some(state) = stream.take() {
state.task.abort();
}
}
_ => {}
}
}
#[allow(clippy::too_many_arguments)]
async fn run_stream(
rt: Runtime,
path: PathAndQuery,
headers: HeaderMap,
timeout: Option<std::time::Duration>,
req_rx: mpsc::Receiver<Bytes>,
outbound_tx: mpsc::Sender<TungMessage>,
json: bool,
annotation: Option<Arc<WsBinding>>,
) {
let method_path = path.path().to_string();
let transcoder = if json {
match rt.schema.transcoder(&method_path).await {
Ok(tc) => Some(tc),
Err(status) => {
send_reset(&outbound_tx, json, status.code(), status.message()).await;
let _ = outbound_tx.send(close_normal()).await;
return;
}
}
} else {
None
};
let req_tc = transcoder.clone();
let req_ann = annotation.clone();
let req_path = method_path.clone();
let body_stream = ReceiverStream::new(req_rx).map(move |payload| {
let proto = if let Some(ann) = &req_ann {
ann.build_message(&payload).map(Bytes::from).unwrap_or_default()
} else if json {
req_tc.as_ref().unwrap().request_json_to_proto(&req_path, &payload).map(Bytes::from).unwrap_or_default()
} else {
payload
};
Ok::<_, Infallible>(BodyFrame::data(grpc_frame(&proto)))
});
let req_body = TonicBody::new(StreamBody::new(body_stream));
let mut builder = Request::builder().method(http::Method::POST).uri(path);
for (name, value) in headers.iter() {
builder = builder.header(name.clone(), value.clone());
}
builder = builder.header(http::header::CONTENT_TYPE, CT_GRPC).header("te", "trailers");
if let Some(d) = timeout {
builder = builder.header("grpc-timeout", metadata::format_grpc_timeout(d + DEADLINE_GRACE));
}
let request = builder.body(req_body).expect("valid request");
let pump = async {
let resp = match rt.backend.call(request).await {
Ok(r) => r,
Err(e) => {
send_reset(&outbound_tx, json, Code::Unavailable, &format!("upstream: {e}")).await;
return;
}
};
let (parts, mut body) = resp.into_parts();
let header = Header {
headers: metadata::metadata_to_vec(&MetadataMap::from_headers(parts.headers.clone())),
};
let _ = outbound_tx.send(to_tung(&Frame { kind: Some(Kind::Header(header)) }, json)).await;
let mut deframer = Deframer::new();
let mut trailers = HeaderMap::new();
while let Some(frame) = body.frame().await {
let frame = match frame {
Ok(f) => f,
Err(_) => break,
};
if frame.is_data() {
let data = frame.into_data().unwrap_or_default();
deframer.push(&data);
while let Some(msg) = deframer.next_message() {
let payload = if json {
match transcoder.as_ref().unwrap().response_proto_to_json(&method_path, &msg) {
Ok(j) => j.into(),
Err(e) => {
send_reset(&outbound_tx, json, Code::Internal, &format!("json encode: {e}")).await;
return;
}
}
} else {
msg
};
let out = Frame { kind: Some(Kind::Message(WsMessage { payload })) };
if outbound_tx.send(to_tung(&out, json)).await.is_err() {
return;
}
}
} else if frame.is_trailers() {
if let Ok(t) = frame.into_trailers() {
trailers = t;
}
}
}
let (status_code, status_message) = metadata::read_status(&trailers, &parts.headers);
let trailer = Trailer {
status_code,
status_message,
trailers: metadata::metadata_to_vec(&MetadataMap::from_headers(trailers)),
};
let _ = outbound_tx.send(to_tung(&Frame { kind: Some(Kind::Trailer(trailer)) }, json)).await;
};
match timeout {
Some(d) => {
if tokio::time::timeout(d, pump).await.is_err() {
let trailer = Trailer {
status_code: Code::DeadlineExceeded as u32,
status_message: "deadline exceeded".into(),
trailers: vec![],
};
let _ = outbound_tx.send(to_tung(&Frame { kind: Some(Kind::Trailer(trailer)) }, json)).await;
}
}
None => pump.await,
}
let _ = outbound_tx.send(close_normal()).await;
}
async fn send_reset(outbound_tx: &mpsc::Sender<TungMessage>, json: bool, code: Code, message: &str) {
let frame = Frame {
kind: Some(Kind::Reset(Reset {
status_code: code as u32,
status_message: message.to_string(),
})),
};
let _ = outbound_tx.send(to_tung(&frame, json)).await;
}
fn close_normal() -> TungMessage {
TungMessage::Close(Some(CloseFrame {
code: CloseCode::Normal,
reason: String::new().into(),
}))
}
fn close_for_status(status: &Status) -> TungMessage {
let code = 4000u16 + status.code() as u16;
TungMessage::Close(Some(CloseFrame {
code: CloseCode::from(code),
reason: truncate_utf8(status.message(), 123).to_string().into(),
}))
}
fn truncate_utf8(s: &str, max: usize) -> &str {
if s.len() <= max {
return s;
}
let mut end = max;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
&s[..end]
}
pub fn decode_binary(data: &[u8], method_url: &str, opened: &mut bool) -> Option<Frame> {
let mut frame = decode_frame(data).ok()?;
match frame.kind.as_mut()? {
Kind::Subscribe(s) => {
s.method = method_url.to_string();
*opened = true;
}
_ if !*opened => return None,
_ => {}
}
Some(frame)
}
pub fn decode_text(text: &str, method_url: &str, opened: &mut bool) -> Option<Frame> {
let jf = decode_json_frame(text).ok()?;
if !*opened {
*opened = true;
let sub = json_open_to_subscribe(jf, method_url.to_string());
Some(Frame { kind: Some(Kind::Subscribe(sub)) })
} else {
Some(json_frame_to_proto(jf))
}
}
pub fn to_tung(frame: &Frame, json: bool) -> TungMessage {
if json {
if let Some(jf) = proto_frame_to_json(frame) {
return TungMessage::Text(encode_json_frame(&jf).into());
}
}
TungMessage::Binary(encode_frame(frame))
}
pub fn keepalive_interval(period: std::time::Duration) -> tokio::time::Interval {
let mut i = tokio::time::interval_at(tokio::time::Instant::now() + period, period);
i.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
i
}
pub async fn next_tick(interval: Option<&mut tokio::time::Interval>) {
match interval {
Some(i) => {
i.tick().await;
}
None => std::future::pending().await,
}
}
pub async fn sleep_until(deadline: Option<tokio::time::Instant>) {
match deadline {
Some(d) => tokio::time::sleep_until(d).await,
None => std::future::pending().await,
}
}