pub mod connect;
pub mod grpc;
pub mod grpc_web;
use std::time::Duration;
use bytes::Bytes;
use foundation_netio::shared::http::{
SimpleHeaders, SimpleIncomingRequest, SimpleMethod, SimpleOutgoingResponse,
};
use crate::shared::compression::CompressionRegistry;
use crate::shared::context::{CancelSignal, Spec, StreamType};
use crate::shared::error::ConnectResult;
use crate::shared::transport::{
BoxFuture, ByteSink, ByteSource, ClientConn, HandlerConn, ProtocolKind, TransportStream,
};
pub type BoxedTask = BoxFuture<'static, ConnectResult<()>>;
pub struct UnaryOutcome {
pub frame: Bytes,
pub headers: SimpleHeaders,
pub trailers: SimpleHeaders,
}
pub struct HandlerExchange {
pub conn: Box<dyn HandlerConn>,
pub reader_task: BoxedTask,
pub writer_task: BoxedTask,
}
pub struct ClientExchange {
pub conn: Box<dyn ClientConn>,
pub reader_task: BoxedTask,
pub writer_task: BoxedTask,
}
pub trait ProtocolHandler: Send + Sync {
fn kind(&self) -> ProtocolKind;
fn allowed_methods(&self) -> &[SimpleMethod];
fn content_types(&self) -> Vec<String>;
fn parse_timeout(&self, headers: &SimpleHeaders) -> Option<Duration>;
fn can_handle(&self, request: &SimpleIncomingRequest) -> bool;
fn codec_name(&self, request: &SimpleIncomingRequest) -> Option<String>;
fn new_conn(
&self,
request: &SimpleIncomingRequest,
spec: Spec,
body: ByteSource,
responder: ByteSink,
compression: &CompressionRegistry,
) -> ConnectResult<HandlerExchange>;
fn decode_unary_request(
&self,
request: &SimpleIncomingRequest,
body: Bytes,
compression: &CompressionRegistry,
) -> ConnectResult<Bytes>;
fn streaming_response_content_type(
&self,
request: &SimpleIncomingRequest,
codec_name: &str,
) -> String;
fn streaming_response_encoding(
&self,
request: &SimpleIncomingRequest,
compression: &CompressionRegistry,
) -> Option<(String, String)>;
fn encode_unary_response(
&self,
response: &mut SimpleOutgoingResponse,
request: &SimpleIncomingRequest,
codec_name: &str,
outcome: UnaryOutcome,
compression: &CompressionRegistry,
) -> ConnectResult<()>;
}
pub trait ProtocolClient: Send + Sync {
fn write_request_headers(
&self,
stream_type: StreamType,
headers: &mut SimpleHeaders,
codec_name: &str,
compression: Option<&str>,
);
fn encode_unary_request(&self, body: &[u8], is_compressed: bool) -> Bytes;
fn decode_unary_response(&self, body: Bytes) -> ConnectResult<Bytes>;
fn new_conn(
&self,
spec: &Spec,
headers: SimpleHeaders,
stream: TransportStream,
cancel: CancelSignal,
) -> ConnectResult<ClientExchange>;
}
#[must_use]
pub fn canonicalize_content_type(content_type: &str) -> String {
content_type
.split(';')
.next()
.unwrap_or(content_type)
.trim()
.to_ascii_lowercase()
}
const DEFAULT_CODEC: &str = "proto";
#[must_use]
pub fn codec_for_content_type<'a>(canonical: &'a str, prefix: &str) -> Option<&'a str> {
let rest = canonical.strip_prefix(prefix)?;
match rest.as_bytes().first() {
None => Some(DEFAULT_CODEC),
Some(b'+') => match &rest[1..] {
"" => None,
codec => Some(codec),
},
Some(_) => None,
}
}
#[must_use]
pub fn parse_connect_content_type(content_type: &str) -> Option<(String, bool)> {
let canonical = canonicalize_content_type(content_type);
let rest = canonical.strip_prefix("application/")?;
match rest.strip_prefix("connect+") {
Some(codec) => Some((codec.to_string(), true)),
None => {
let codec = rest.to_string();
if codec.contains('+') || codec == "grpc" || codec == "grpc-web" {
None
} else {
Some((codec, false))
}
}
}
}