1use std::future::Future;
12use std::net::SocketAddr;
13use std::sync::Arc;
14use std::time::Duration;
15
16use bytes::Bytes;
17use http::HeaderMap;
18use http_body_util::combinators::UnsyncBoxBody;
19use hyper::body::Incoming;
20use hyper::Request;
21use hyper_util::rt::{TokioExecutor, TokioIo};
22use tokio::net::TcpListener;
23use tonic::service::Routes;
24use tonic::transport::Channel;
25
26pub mod backend;
28mod drain;
29mod fetch;
30mod h2ts;
31mod reflect;
32pub mod schema;
33mod ws;
34
35pub mod pb {
38 include!(concat!(env!("OUT_DIR"), "/grpc.webnext.v1.rs"));
39}
40pub mod codec;
41pub mod frame;
42mod framing;
43pub mod grpc_framing;
44pub mod httprule;
45pub mod json_frame;
46pub mod metadata;
47pub mod transcode;
48
49pub(crate) use drain::Drain;
50
51pub use backend::Backend;
52pub use schema::{Schema, SchemaSource};
53
54pub use codec::BytesCodec;
55pub use frame::{decode_frame, encode_frame, FrameError};
56pub use framing::{
57 decode_response_body, encode_request_body, encode_response_body, encode_trailer_block,
58 FetchError, EMPTY_MESSAGE_BLOCK, LEN_PREFIX,
59};
60pub use grpc_framing::{deframe_all, frame as grpc_frame, Deframer};
61pub use httprule::{HttpCall, HttpRouter, WsBinding};
62pub use transcode::{TranscodeError, Transcoder};
63
64pub const CT_PROTO: &str = "application/grpc-webnext+proto";
67pub const CT_JSON: &str = "application/grpc-webnext+json";
68pub(crate) const CT_GRPC: &str = "application/grpc";
69
70pub const WS_SUBPROTOCOL: &str = "grpc-webnext";
72pub const WS_SUBPROTOCOL_JSON: &str = "grpc-webnext+json";
73pub const WS_SUBPROTOCOL_PROTO: &str = "grpc-webnext+proto";
74
75pub const DEADLINE_GRACE: Duration = Duration::from_millis(500);
79
80pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
81pub type ResBody = UnsyncBoxBody<Bytes, BoxError>;
82
83#[derive(Clone)]
94pub struct ServerConfig {
95 pub max_message_bytes: usize,
96 pub transcoder: Option<Arc<Transcoder>>,
100 pub allow_implicit_codec: bool,
103 pub ws_keepalive: Option<Duration>,
105 pub ws_keepalive_timeout: Duration,
107}
108
109impl Default for ServerConfig {
110 fn default() -> Self {
111 Self {
112 max_message_bytes: 4 * 1024 * 1024,
113 transcoder: None,
114 allow_implicit_codec: false,
115 ws_keepalive: None,
116 ws_keepalive_timeout: Duration::from_secs(20),
117 }
118 }
119}
120
121#[derive(Clone)]
123pub struct ProxyConfig {
124 pub upstream: http::Uri,
126 pub max_message_bytes: usize,
127 pub ws_keepalive: Option<Duration>,
128 pub ws_keepalive_timeout: Duration,
129 pub schema: SchemaSource,
131 pub reflection_ttl: Duration,
133 pub admin_reload_path: Option<String>,
135 pub allow_implicit_codec: bool,
137}
138
139impl Default for ProxyConfig {
140 fn default() -> Self {
141 Self {
142 upstream: http::Uri::default(),
143 max_message_bytes: 4 * 1024 * 1024,
144 ws_keepalive: None,
145 ws_keepalive_timeout: Duration::from_secs(20),
146 schema: SchemaSource::None,
147 reflection_ttl: Duration::from_secs(4 * 60 * 60),
148 admin_reload_path: None,
149 allow_implicit_codec: false,
150 }
151 }
152}
153
154pub(crate) struct RunConfig {
159 pub max_message_bytes: usize,
160 pub ws_keepalive: Option<Duration>,
161 pub ws_keepalive_timeout: Duration,
162 pub allow_implicit_codec: bool,
163 pub admin_reload_path: Option<String>,
164 pub upstream_authority: Option<String>,
166}
167
168#[derive(Clone)]
175pub(crate) struct Runtime {
176 pub backend: Backend,
177 pub schema: Schema,
178 pub cfg: Arc<RunConfig>,
179 pub drain: Drain,
180}
181
182pub async fn serve_in_process(
187 listener: TcpListener,
188 routes: Routes,
189 config: ServerConfig,
190) -> std::io::Result<()> {
191 serve_in_process_with_shutdown(listener, routes, config, std::future::pending()).await
192}
193
194pub async fn serve_in_process_with_shutdown(
220 listener: TcpListener,
221 routes: Routes,
222 config: ServerConfig,
223 shutdown: impl Future<Output = ()>,
224) -> std::io::Result<()> {
225 let schema = Schema::from_transcoder(config.transcoder.clone());
226 let cfg = Arc::new(RunConfig {
227 max_message_bytes: config.max_message_bytes,
228 ws_keepalive: config.ws_keepalive,
229 ws_keepalive_timeout: config.ws_keepalive_timeout,
230 allow_implicit_codec: config.allow_implicit_codec,
231 admin_reload_path: None,
232 upstream_authority: None,
233 });
234 let (controller, drain) = drain::channel();
235 let rt = Runtime { backend: Backend::InProcess(routes), schema, cfg, drain };
236 run(listener, rt, controller, shutdown).await
237}
238
239pub async fn serve_proxy(listener: TcpListener, config: ProxyConfig) -> std::io::Result<()> {
241 serve_proxy_with_shutdown(listener, config, std::future::pending()).await
242}
243
244pub async fn serve_proxy_with_shutdown(
252 listener: TcpListener,
253 config: ProxyConfig,
254 shutdown: impl Future<Output = ()>,
255) -> std::io::Result<()> {
256 let channel = Channel::builder(config.upstream.clone()).connect_lazy();
258 let upstream_authority = config.upstream.authority().map(|a| match a.port_u16() {
260 Some(_) => a.to_string(),
261 None => {
262 let port = if config.upstream.scheme_str() == Some("https") { 443 } else { 80 };
263 format!("{}:{}", a.host(), port)
264 }
265 });
266 let schema = Schema::build(config.schema.clone(), channel.clone(), config.reflection_ttl)
268 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e.to_string()))?;
269 schema.start();
271 let cfg = Arc::new(RunConfig {
272 max_message_bytes: config.max_message_bytes,
273 ws_keepalive: config.ws_keepalive,
274 ws_keepalive_timeout: config.ws_keepalive_timeout,
275 allow_implicit_codec: config.allow_implicit_codec,
276 admin_reload_path: config.admin_reload_path,
277 upstream_authority,
278 });
279 let (controller, drain) = drain::channel();
280 let rt = Runtime { backend: Backend::Upstream(channel), schema, cfg, drain };
281 run(listener, rt, controller, shutdown).await
282}
283
284async fn run(
286 listener: TcpListener,
287 rt: Runtime,
288 controller: drain::DrainController,
289 shutdown: impl Future<Output = ()>,
290) -> std::io::Result<()> {
291 let mut shutdown = std::pin::pin!(shutdown);
292 let result = loop {
293 let accepted = tokio::select! {
294 accepted = listener.accept() => accepted,
295 _ = shutdown.as_mut() => break Ok(()),
296 };
297 let (stream, _peer) = match accepted {
298 Ok(pair) => pair,
299 Err(e) => break Err(e),
300 };
301 let io = TokioIo::new(stream);
302 let rt = rt.clone();
303 tokio::spawn(async move {
304 let drain = rt.drain.clone();
305 let service = hyper::service::service_fn(move |req: Request<Incoming>| {
306 let rt = rt.clone();
307 async move { fetch::handle(&rt, req).await }
308 });
309 let builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new());
311 let conn = builder.serve_connection_with_upgrades(io, service);
312 if let Err(e) = drain::serve_graceful!(&drain, conn) {
315 tracing::debug!("connection error: {e}");
316 }
317 });
318 };
319
320 drop(listener);
323 drop(rt);
324 controller.finish().await;
325 result
326}
327
328pub async fn bind_and_serve_in_process(
331 routes: Routes,
332 config: ServerConfig,
333) -> std::io::Result<(SocketAddr, tokio::task::JoinHandle<std::io::Result<()>>)> {
334 let listener = TcpListener::bind("127.0.0.1:0").await?;
335 let addr = listener.local_addr()?;
336 let handle = tokio::spawn(serve_in_process(listener, routes, config));
337 Ok((addr, handle))
338}
339
340pub async fn bind_and_serve_proxy(
342 config: ProxyConfig,
343) -> std::io::Result<(SocketAddr, tokio::task::JoinHandle<std::io::Result<()>>)> {
344 let listener = TcpListener::bind("127.0.0.1:0").await?;
345 let addr = listener.local_addr()?;
346 let handle = tokio::spawn(serve_proxy(listener, config));
347 Ok((addr, handle))
348}
349
350pub fn ws_subprotocols(headers: &HeaderMap) -> Vec<String> {
355 headers
356 .get(http::header::SEC_WEBSOCKET_PROTOCOL)
357 .and_then(|v| v.to_str().ok())
358 .map(|s| s.split(',').map(|t| t.trim().to_string()).filter(|t| !t.is_empty()).collect())
359 .unwrap_or_default()
360}