Skip to content

Commit 9917177

Browse files
andrewhavckeaufavor
authored andcommitted
Add support for gRPC-web module to bridge gRPC-web client requests to gRPC server requests
1 parent 760dda4 commit 9917177

File tree

11 files changed

+623
-19
lines changed

11 files changed

+623
-19
lines changed

.bleep

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
9b92c0fed7b703c61415414c69ff196e9deb11eb
1+
761f676b044dcf0d34205f96921e3385ffac7810
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2024 Cloudflare, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use super::*;
16+
use crate::protocols::http::bridge::grpc_web::GrpcWebCtx;
17+
use std::ops::{Deref, DerefMut};
18+
19+
/// gRPC-web bridge module, this will convert
20+
/// HTTP/1.1 gRPC-web requests to H2 gRPC requests
21+
#[derive(Default)]
22+
pub struct GrpcWebBridge(GrpcWebCtx);
23+
24+
impl Deref for GrpcWebBridge {
25+
type Target = GrpcWebCtx;
26+
27+
fn deref(&self) -> &Self::Target {
28+
&self.0
29+
}
30+
}
31+
32+
impl DerefMut for GrpcWebBridge {
33+
fn deref_mut(&mut self) -> &mut Self::Target {
34+
&mut self.0
35+
}
36+
}
37+
38+
#[async_trait]
39+
impl HttpModule for GrpcWebBridge {
40+
fn as_any(&self) -> &dyn std::any::Any {
41+
self
42+
}
43+
44+
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
45+
self
46+
}
47+
48+
async fn request_header_filter(&mut self, req: &mut RequestHeader) -> Result<()> {
49+
self.0.request_header_filter(req);
50+
Ok(())
51+
}
52+
53+
async fn response_header_filter(
54+
&mut self,
55+
resp: &mut ResponseHeader,
56+
_end_of_stream: bool,
57+
) -> Result<()> {
58+
self.0.response_header_filter(resp);
59+
Ok(())
60+
}
61+
62+
fn response_trailer_filter(
63+
&mut self,
64+
trailers: &mut Option<Box<HeaderMap>>,
65+
) -> Result<Option<Bytes>> {
66+
if let Some(trailers) = trailers {
67+
return self.0.response_trailer_filter(trailers);
68+
}
69+
Ok(None)
70+
}
71+
}
72+
73+
/// The builder for gRPC-web bridge module
74+
pub struct GrpcWeb;
75+
76+
impl HttpModuleBuilder for GrpcWeb {
77+
fn init(&self) -> Module {
78+
Box::new(GrpcWebBridge::default())
79+
}
80+
}

pingora-core/src/modules/http/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
//! See the [ResponseCompression] module for an example of how to implement a basic module.
2020
2121
pub mod compression;
22+
pub mod grpc_web;
2223

2324
use async_trait::async_trait;
2425
use bytes::Bytes;
26+
use http::HeaderMap;
2527
use once_cell::sync::OnceCell;
2628
use pingora_error::Result;
2729
use pingora_http::{RequestHeader, ResponseHeader};
@@ -61,6 +63,13 @@ pub trait HttpModule {
6163
Ok(())
6264
}
6365

66+
fn response_trailer_filter(
67+
&mut self,
68+
_trailers: &mut Option<Box<HeaderMap>>,
69+
) -> Result<Option<Bytes>> {
70+
Ok(None)
71+
}
72+
6473
fn as_any(&self) -> &dyn Any;
6574
fn as_any_mut(&mut self) -> &mut dyn Any;
6675
}
@@ -226,6 +235,27 @@ impl HttpModuleCtx {
226235
}
227236
Ok(())
228237
}
238+
239+
/// Run the `response_trailer_filter` for all the modules according to their orders.
240+
///
241+
/// Returns an `Option<Bytes>` which can be used to write response trailers into
242+
/// the response body. Note, if multiple modules attempt to write trailers into
243+
/// the body the last one will be used.
244+
///
245+
/// Implementors that intend to write trailers into the body need to ensure their filter
246+
/// is using an encoding that supports this.
247+
pub fn response_trailer_filter(
248+
&mut self,
249+
trailers: &mut Option<Box<HeaderMap>>,
250+
) -> Result<Option<Bytes>> {
251+
let mut encoded = None;
252+
for filter in self.module_ctx.iter_mut() {
253+
if let Some(buf) = filter.response_trailer_filter(trailers)? {
254+
encoded = Some(buf);
255+
}
256+
}
257+
Ok(encoded)
258+
}
229259
}
230260

231261
#[cfg(test)]

0 commit comments

Comments
 (0)