rws_proxy/proxy/
grpc_web.rs1use std::io::Read;
13use std::sync::{Arc, Mutex};
14
15use crate::application::Application;
16use crate::core::base64::Base64;
17use crate::header::Header;
18use crate::middleware::Middleware;
19use crate::request::{Body, Request};
20use crate::response::{Response, Trailers};
21use crate::server::ConnectionInfo;
22
23#[derive(Default)]
37pub struct GrpcWebLayer;
38
39impl GrpcWebLayer {
40 pub fn new() -> Self {
41 GrpcWebLayer
42 }
43}
44
45struct Variant {
48 text: bool,
49 suffix: String,
50}
51
52fn variant(request: &Request) -> Option<Variant> {
53 let content_type = request.header(Header::_CONTENT_TYPE)?.value.as_ref().trim().to_ascii_lowercase();
54 if let Some(suffix) = content_type.strip_prefix("application/grpc-web-text") {
55 return Some(Variant { text: true, suffix: suffix.to_string() });
56 }
57 content_type.strip_prefix("application/grpc-web").map(|suffix| Variant { text: false, suffix: suffix.to_string() })
58}
59
60impl Middleware for GrpcWebLayer {
61 fn handle(&self, request: &Request, connection: &ConnectionInfo, next: &dyn Application) -> Result<Response, String> {
62 let Some(variant) = variant(request) else {
63 return next.execute(request, connection);
64 };
65 let mut grpc = request.clone();
66 grpc.headers.retain(|h| {
67 !(h.name.eq_ignore_ascii_case("content-type") || h.name.eq_ignore_ascii_case("te")
68 || (variant.text && h.name.eq_ignore_ascii_case("content-length")))
69 });
70 grpc.headers.push(Header::new(Header::_CONTENT_TYPE, format!("application/grpc{}", variant.suffix)));
71 grpc.headers.push(Header::new("te", "trailers"));
72 if variant.text {
73 grpc.body = match std::mem::take(&mut grpc.body) {
74 Body::Bytes(bytes) => Body::Bytes(decode_text(&bytes).map_err(|e| format!("grpc-web-text request body: {e}"))?),
75 stream => Body::Stream(Arc::new(Mutex::new(Box::new(Base64Decoder::new(stream.into_reader()))))),
76 };
77 }
78 let response = next.execute(&grpc, connection)?;
79 Ok(to_grpc_web(response, variant.text))
80 }
81
82 fn accepts_streamed_body(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
84 true
85 }
86}
87
88fn to_grpc_web(mut response: Response, text: bool) -> Response {
91 let web_type = |grpc: &str| {
92 let suffix = grpc.trim().strip_prefix("application/grpc").unwrap_or("");
93 format!("application/grpc-web{}{suffix}", if text { "-text" } else { "" })
94 };
95 let mut is_grpc = false;
96 for header in response.headers.iter_mut() {
97 if header.name.eq_ignore_ascii_case("content-type") && header.value.starts_with("application/grpc") {
98 header.value = web_type(&header.value).into();
99 is_grpc = true;
100 }
101 }
102 for range in response.content_range_list.iter_mut() {
103 if range.content_type.starts_with("application/grpc") {
104 range.content_type = web_type(&range.content_type);
105 is_grpc = true;
106 }
107 }
108 if !is_grpc {
109 return response; }
111 let trailers = response.trailers.take();
112 if let Some(body) = response.stream_pipe.take() {
113 let framed: Box<dyn Read + Send> = Box::new(TrailerFrame { body, trailers, frame: None });
114 response.stream_pipe = Some(if text { Box::new(Base64Encoder::new(framed)) } else { framed });
115 } else {
116 let mut body = Response::generate_body(std::mem::take(&mut response.content_range_list));
118 if let Some(trailers) = &trailers {
119 body.extend_from_slice(&trailer_frame(&trailers.get()));
120 }
121 if text {
122 body = Base64::encode(&body).unwrap_or_default().into_bytes();
123 }
124 let content_type = response
125 .headers
126 .iter()
127 .find(|h| h.name.eq_ignore_ascii_case("content-type"))
128 .map_or_else(|| web_type("application/grpc"), |h| h.value.to_string());
129 response.headers.retain(|h| !h.name.eq_ignore_ascii_case("content-type"));
130 if !body.is_empty() {
131 response.content_range_list = vec![crate::range::Range::get_content_range(body, content_type)];
132 } else {
133 response.headers.push(Header::new(Header::_CONTENT_TYPE, content_type));
134 }
135 }
136 response
137}
138
139#[doc(hidden)]
142pub fn trailer_frame(trailers: &[Header]) -> Vec<u8> {
143 if trailers.is_empty() {
144 return Vec::new();
145 }
146 let mut lines = Vec::new();
147 for header in trailers {
148 lines.extend(header.name.bytes().map(|b| b.to_ascii_lowercase()));
149 lines.push(b':');
150 lines.extend_from_slice(header.value.trim().as_bytes());
151 lines.extend_from_slice(b"\r\n");
152 }
153 let mut frame = Vec::with_capacity(5 + lines.len());
154 frame.push(0x80);
155 frame.extend_from_slice(&(lines.len() as u32).to_be_bytes());
156 frame.extend_from_slice(&lines);
157 frame
158}
159
160struct TrailerFrame {
163 body: Box<dyn Read + Send>,
164 trailers: Option<Trailers>,
165 frame: Option<std::io::Cursor<Vec<u8>>>,
166}
167
168impl Read for TrailerFrame {
169 fn read(&mut self, out: &mut [u8]) -> std::io::Result<usize> {
170 if self.frame.is_none() {
171 let n = self.body.read(out)?;
172 if n > 0 {
173 return Ok(n);
174 }
175 let fields = self.trailers.as_ref().map(|t| t.get()).unwrap_or_default();
176 self.frame = Some(std::io::Cursor::new(trailer_frame(&fields)));
177 }
178 self.frame.as_mut().map_or(Ok(0), |frame| frame.read(out))
179 }
180}
181
182struct Base64Encoder {
185 inner: Box<dyn Read + Send>,
186 raw: Vec<u8>,
187 encoded: std::io::Cursor<Vec<u8>>,
188 ended: bool,
189}
190
191impl Base64Encoder {
192 fn new(inner: Box<dyn Read + Send>) -> Self {
193 Base64Encoder { inner, raw: Vec::new(), encoded: std::io::Cursor::new(Vec::new()), ended: false }
194 }
195}
196
197impl Read for Base64Encoder {
198 fn read(&mut self, out: &mut [u8]) -> std::io::Result<usize> {
199 loop {
200 let n = self.encoded.read(out)?;
201 if n > 0 || self.ended {
202 return Ok(n);
203 }
204 let mut chunk = vec![0u8; 16 * 1024];
205 let read = self.inner.read(&mut chunk)?;
206 self.raw.extend_from_slice(&chunk[..read]);
207 let take = if read == 0 {
208 self.ended = true;
209 self.raw.len()
210 } else {
211 self.raw.len() - self.raw.len() % 3
212 };
213 let group: Vec<u8> = self.raw.drain(..take).collect();
214 let text = Base64::encode(&group).map_err(std::io::Error::other)?;
215 self.encoded = std::io::Cursor::new(text.into_bytes());
216 }
217 }
218}
219
220struct Base64Decoder {
223 inner: Box<dyn Read + Send>,
224 text: Vec<u8>,
225 decoded: std::io::Cursor<Vec<u8>>,
226 ended: bool,
227}
228
229impl Base64Decoder {
230 fn new(inner: Box<dyn Read + Send>) -> Self {
231 Base64Decoder { inner, text: Vec::new(), decoded: std::io::Cursor::new(Vec::new()), ended: false }
232 }
233}
234
235impl Read for Base64Decoder {
236 fn read(&mut self, out: &mut [u8]) -> std::io::Result<usize> {
237 loop {
238 let n = self.decoded.read(out)?;
239 if n > 0 || self.ended {
240 return Ok(n);
241 }
242 let mut chunk = vec![0u8; 16 * 1024];
243 let read = self.inner.read(&mut chunk)?;
244 self.text.extend(chunk[..read].iter().filter(|b| !b.is_ascii_whitespace()));
245 let take = if read == 0 {
246 self.ended = true;
247 if self.text.len() % 4 != 0 {
248 return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "grpc-web-text body isn't whole base64"));
249 }
250 self.text.len()
251 } else {
252 self.text.len() - self.text.len() % 4
253 };
254 let groups: Vec<u8> = self.text.drain(..take).collect();
255 let bytes = decode_text(&groups).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
256 self.decoded = std::io::Cursor::new(bytes);
257 }
258 }
259}
260
261fn decode_text(text: &[u8]) -> Result<Vec<u8>, String> {
263 let text: Vec<u8> = text.iter().copied().filter(|b| !b.is_ascii_whitespace()).collect();
264 if text.len() % 4 != 0 {
265 return Err("not whole base64".to_string());
266 }
267 let mut out = Vec::with_capacity(text.len() / 4 * 3);
268 let mut start = 0;
269 for (i, group) in text.chunks(4).enumerate() {
270 if group.contains(&b'=') {
272 let end = (i + 1) * 4;
273 let segment = String::from_utf8(text[start..end].to_vec()).map_err(|e| e.to_string())?;
274 out.extend(Base64::decode(segment)?);
275 start = end;
276 }
277 }
278 if start < text.len() {
279 let segment = String::from_utf8(text[start..].to_vec()).map_err(|e| e.to_string())?;
280 out.extend(Base64::decode(segment)?);
281 }
282 Ok(out)
283}
284
285#[cfg(test)]
286mod tests;