use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use bytes::Bytes;
use http::HeaderMap;
use http_body_util::combinators::UnsyncBoxBody;
use hyper::body::Incoming;
use hyper::Request;
use hyper_util::rt::{TokioExecutor, TokioIo};
use tokio::net::TcpListener;
use tonic::service::Routes;
use tonic::transport::Channel;
pub mod backend;
mod fetch;
mod h2ts;
mod reflect;
pub mod schema;
mod ws;
pub mod pb {
include!(concat!(env!("OUT_DIR"), "/grpc.webnext.v1.rs"));
}
pub mod codec;
pub mod frame;
mod framing;
pub mod grpc_framing;
pub mod httprule;
pub mod json_frame;
pub mod metadata;
pub mod transcode;
pub use backend::Backend;
pub use schema::{Schema, SchemaSource};
pub use codec::BytesCodec;
pub use frame::{decode_frame, encode_frame, FrameError};
pub use framing::{
decode_response_body, encode_request_body, encode_response_body, encode_trailer_block,
FetchError, EMPTY_MESSAGE_BLOCK, LEN_PREFIX,
};
pub use grpc_framing::{deframe_all, frame as grpc_frame, Deframer};
pub use httprule::{HttpCall, HttpRouter, WsBinding};
pub use transcode::{TranscodeError, Transcoder};
pub const CT_PROTO: &str = "application/grpc-webnext+proto";
pub const CT_JSON: &str = "application/grpc-webnext+json";
pub(crate) const CT_GRPC: &str = "application/grpc";
pub const WS_SUBPROTOCOL: &str = "grpc-webnext";
pub const WS_SUBPROTOCOL_JSON: &str = "grpc-webnext+json";
pub const WS_SUBPROTOCOL_PROTO: &str = "grpc-webnext+proto";
pub const DEADLINE_GRACE: Duration = Duration::from_millis(500);
pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
pub type ResBody = UnsyncBoxBody<Bytes, BoxError>;
#[derive(Clone)]
pub struct ServerConfig {
pub max_message_bytes: usize,
pub transcoder: Option<Arc<Transcoder>>,
pub allow_implicit_codec: bool,
pub ws_keepalive: Option<Duration>,
pub ws_keepalive_timeout: Duration,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
max_message_bytes: 4 * 1024 * 1024,
transcoder: None,
allow_implicit_codec: false,
ws_keepalive: None,
ws_keepalive_timeout: Duration::from_secs(20),
}
}
}
#[derive(Clone)]
pub struct ProxyConfig {
pub upstream: http::Uri,
pub max_message_bytes: usize,
pub ws_keepalive: Option<Duration>,
pub ws_keepalive_timeout: Duration,
pub schema: SchemaSource,
pub reflection_ttl: Duration,
pub admin_reload_path: Option<String>,
pub allow_implicit_codec: bool,
}
impl Default for ProxyConfig {
fn default() -> Self {
Self {
upstream: http::Uri::default(),
max_message_bytes: 4 * 1024 * 1024,
ws_keepalive: None,
ws_keepalive_timeout: Duration::from_secs(20),
schema: SchemaSource::None,
reflection_ttl: Duration::from_secs(4 * 60 * 60),
admin_reload_path: None,
allow_implicit_codec: false,
}
}
}
pub(crate) struct RunConfig {
pub max_message_bytes: usize,
pub ws_keepalive: Option<Duration>,
pub ws_keepalive_timeout: Duration,
pub allow_implicit_codec: bool,
pub admin_reload_path: Option<String>,
pub upstream_authority: Option<String>,
}
#[derive(Clone)]
pub(crate) struct Runtime {
pub backend: Backend,
pub schema: Schema,
pub cfg: Arc<RunConfig>,
}
pub async fn serve_in_process(
listener: TcpListener,
routes: Routes,
config: ServerConfig,
) -> std::io::Result<()> {
let schema = Schema::from_transcoder(config.transcoder.clone());
let cfg = Arc::new(RunConfig {
max_message_bytes: config.max_message_bytes,
ws_keepalive: config.ws_keepalive,
ws_keepalive_timeout: config.ws_keepalive_timeout,
allow_implicit_codec: config.allow_implicit_codec,
admin_reload_path: None,
upstream_authority: None,
});
run(listener, Runtime { backend: Backend::InProcess(routes), schema, cfg }).await
}
pub async fn serve_proxy(listener: TcpListener, config: ProxyConfig) -> std::io::Result<()> {
let channel = Channel::builder(config.upstream.clone()).connect_lazy();
let upstream_authority = config.upstream.authority().map(|a| match a.port_u16() {
Some(_) => a.to_string(),
None => {
let port = if config.upstream.scheme_str() == Some("https") { 443 } else { 80 };
format!("{}:{}", a.host(), port)
}
});
let schema = Schema::build(config.schema.clone(), channel.clone(), config.reflection_ttl)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e.to_string()))?;
schema.start();
let cfg = Arc::new(RunConfig {
max_message_bytes: config.max_message_bytes,
ws_keepalive: config.ws_keepalive,
ws_keepalive_timeout: config.ws_keepalive_timeout,
allow_implicit_codec: config.allow_implicit_codec,
admin_reload_path: config.admin_reload_path,
upstream_authority,
});
run(listener, Runtime { backend: Backend::Upstream(channel), schema, cfg }).await
}
async fn run(listener: TcpListener, rt: Runtime) -> std::io::Result<()> {
loop {
let (stream, _peer) = listener.accept().await?;
let io = TokioIo::new(stream);
let rt = rt.clone();
tokio::spawn(async move {
let service = hyper::service::service_fn(move |req: Request<Incoming>| {
let rt = rt.clone();
async move { fetch::handle(&rt, req).await }
});
if let Err(e) = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new())
.serve_connection_with_upgrades(io, service)
.await
{
tracing::debug!("connection error: {e}");
}
});
}
}
pub async fn bind_and_serve_in_process(
routes: Routes,
config: ServerConfig,
) -> std::io::Result<(SocketAddr, tokio::task::JoinHandle<std::io::Result<()>>)> {
let listener = TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
let handle = tokio::spawn(serve_in_process(listener, routes, config));
Ok((addr, handle))
}
pub async fn bind_and_serve_proxy(
config: ProxyConfig,
) -> std::io::Result<(SocketAddr, tokio::task::JoinHandle<std::io::Result<()>>)> {
let listener = TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
let handle = tokio::spawn(serve_proxy(listener, config));
Ok((addr, handle))
}
pub fn ws_subprotocols(headers: &HeaderMap) -> Vec<String> {
headers
.get(http::header::SEC_WEBSOCKET_PROTOCOL)
.and_then(|v| v.to_str().ok())
.map(|s| s.split(',').map(|t| t.trim().to_string()).filter(|t| !t.is_empty()).collect())
.unwrap_or_default()
}