1use std::net::SocketAddr;
12use std::sync::Arc;
13use std::time::Duration;
14
15use bytes::Bytes;
16use http::HeaderMap;
17use http_body_util::combinators::UnsyncBoxBody;
18use hyper::body::Incoming;
19use hyper::Request;
20use hyper_util::rt::{TokioExecutor, TokioIo};
21use tokio::net::TcpListener;
22use tonic::service::Routes;
23use tonic::transport::Channel;
24
25pub mod backend;
27mod fetch;
28mod h2ts;
29mod reflect;
30pub mod schema;
31mod ws;
32
33pub mod pb {
36 include!(concat!(env!("OUT_DIR"), "/grpc.webnext.v1.rs"));
37}
38pub mod codec;
39pub mod frame;
40mod framing;
41pub mod grpc_framing;
42pub mod httprule;
43pub mod json_frame;
44pub mod metadata;
45pub mod transcode;
46
47pub use backend::Backend;
48pub use schema::{Schema, SchemaSource};
49
50pub use codec::BytesCodec;
51pub use frame::{decode_frame, encode_frame, FrameError};
52pub use framing::{
53 decode_response_body, encode_request_body, encode_response_body, encode_trailer_block,
54 FetchError, EMPTY_MESSAGE_BLOCK, LEN_PREFIX,
55};
56pub use grpc_framing::{deframe_all, frame as grpc_frame, Deframer};
57pub use httprule::{HttpCall, HttpRouter, WsBinding};
58pub use transcode::{TranscodeError, Transcoder};
59
60pub const CT_PROTO: &str = "application/grpc-webnext+proto";
63pub const CT_JSON: &str = "application/grpc-webnext+json";
64pub(crate) const CT_GRPC: &str = "application/grpc";
65
66pub const WS_SUBPROTOCOL: &str = "grpc-webnext";
68pub const WS_SUBPROTOCOL_JSON: &str = "grpc-webnext+json";
69pub const WS_SUBPROTOCOL_PROTO: &str = "grpc-webnext+proto";
70
71pub const DEADLINE_GRACE: Duration = Duration::from_millis(500);
75
76pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
77pub type ResBody = UnsyncBoxBody<Bytes, BoxError>;
78
79#[derive(Clone)]
90pub struct ServerConfig {
91 pub max_message_bytes: usize,
92 pub transcoder: Option<Arc<Transcoder>>,
96 pub allow_implicit_codec: bool,
99 pub ws_keepalive: Option<Duration>,
101 pub ws_keepalive_timeout: Duration,
103}
104
105impl Default for ServerConfig {
106 fn default() -> Self {
107 Self {
108 max_message_bytes: 4 * 1024 * 1024,
109 transcoder: None,
110 allow_implicit_codec: false,
111 ws_keepalive: None,
112 ws_keepalive_timeout: Duration::from_secs(20),
113 }
114 }
115}
116
117#[derive(Clone)]
119pub struct ProxyConfig {
120 pub upstream: http::Uri,
122 pub max_message_bytes: usize,
123 pub ws_keepalive: Option<Duration>,
124 pub ws_keepalive_timeout: Duration,
125 pub schema: SchemaSource,
127 pub reflection_ttl: Duration,
129 pub admin_reload_path: Option<String>,
131 pub allow_implicit_codec: bool,
133}
134
135impl Default for ProxyConfig {
136 fn default() -> Self {
137 Self {
138 upstream: http::Uri::default(),
139 max_message_bytes: 4 * 1024 * 1024,
140 ws_keepalive: None,
141 ws_keepalive_timeout: Duration::from_secs(20),
142 schema: SchemaSource::None,
143 reflection_ttl: Duration::from_secs(4 * 60 * 60),
144 admin_reload_path: None,
145 allow_implicit_codec: false,
146 }
147 }
148}
149
150pub(crate) struct RunConfig {
155 pub max_message_bytes: usize,
156 pub ws_keepalive: Option<Duration>,
157 pub ws_keepalive_timeout: Duration,
158 pub allow_implicit_codec: bool,
159 pub admin_reload_path: Option<String>,
160 pub upstream_authority: Option<String>,
162}
163
164#[derive(Clone)]
167pub(crate) struct Runtime {
168 pub backend: Backend,
169 pub schema: Schema,
170 pub cfg: Arc<RunConfig>,
171}
172
173pub async fn serve_in_process(
177 listener: TcpListener,
178 routes: Routes,
179 config: ServerConfig,
180) -> std::io::Result<()> {
181 let schema = Schema::from_transcoder(config.transcoder.clone());
182 let cfg = Arc::new(RunConfig {
183 max_message_bytes: config.max_message_bytes,
184 ws_keepalive: config.ws_keepalive,
185 ws_keepalive_timeout: config.ws_keepalive_timeout,
186 allow_implicit_codec: config.allow_implicit_codec,
187 admin_reload_path: None,
188 upstream_authority: None,
189 });
190 run(listener, Runtime { backend: Backend::InProcess(routes), schema, cfg }).await
191}
192
193pub async fn serve_proxy(listener: TcpListener, config: ProxyConfig) -> std::io::Result<()> {
195 let channel = Channel::builder(config.upstream.clone()).connect_lazy();
197 let upstream_authority = config.upstream.authority().map(|a| match a.port_u16() {
199 Some(_) => a.to_string(),
200 None => {
201 let port = if config.upstream.scheme_str() == Some("https") { 443 } else { 80 };
202 format!("{}:{}", a.host(), port)
203 }
204 });
205 let schema = Schema::build(config.schema.clone(), channel.clone(), config.reflection_ttl)
207 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e.to_string()))?;
208 schema.start();
210 let cfg = Arc::new(RunConfig {
211 max_message_bytes: config.max_message_bytes,
212 ws_keepalive: config.ws_keepalive,
213 ws_keepalive_timeout: config.ws_keepalive_timeout,
214 allow_implicit_codec: config.allow_implicit_codec,
215 admin_reload_path: config.admin_reload_path,
216 upstream_authority,
217 });
218 run(listener, Runtime { backend: Backend::Upstream(channel), schema, cfg }).await
219}
220
221async fn run(listener: TcpListener, rt: Runtime) -> std::io::Result<()> {
223 loop {
224 let (stream, _peer) = listener.accept().await?;
225 let io = TokioIo::new(stream);
226 let rt = rt.clone();
227 tokio::spawn(async move {
228 let service = hyper::service::service_fn(move |req: Request<Incoming>| {
229 let rt = rt.clone();
230 async move { fetch::handle(&rt, req).await }
231 });
232 if let Err(e) = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new())
233 .serve_connection_with_upgrades(io, service)
234 .await
235 {
236 tracing::debug!("connection error: {e}");
237 }
238 });
239 }
240}
241
242pub async fn bind_and_serve_in_process(
245 routes: Routes,
246 config: ServerConfig,
247) -> std::io::Result<(SocketAddr, tokio::task::JoinHandle<std::io::Result<()>>)> {
248 let listener = TcpListener::bind("127.0.0.1:0").await?;
249 let addr = listener.local_addr()?;
250 let handle = tokio::spawn(serve_in_process(listener, routes, config));
251 Ok((addr, handle))
252}
253
254pub async fn bind_and_serve_proxy(
256 config: ProxyConfig,
257) -> std::io::Result<(SocketAddr, tokio::task::JoinHandle<std::io::Result<()>>)> {
258 let listener = TcpListener::bind("127.0.0.1:0").await?;
259 let addr = listener.local_addr()?;
260 let handle = tokio::spawn(serve_proxy(listener, config));
261 Ok((addr, handle))
262}
263
264pub fn ws_subprotocols(headers: &HeaderMap) -> Vec<String> {
269 headers
270 .get(http::header::SEC_WEBSOCKET_PROTOCOL)
271 .and_then(|v| v.to_str().ok())
272 .map(|s| s.split(',').map(|t| t.trim().to_string()).filter(|t| !t.is_empty()).collect())
273 .unwrap_or_default()
274}