@@ -42,6 +42,29 @@ async fn binary_request() {
4242 assert_eq ! ( & trailers[ ..] , b"grpc-status:0\r \n " ) ;
4343}
4444
45+ #[ tokio:: test]
46+ async fn binary_request_reverse_proxy ( ) {
47+ let server_url = spawn_reverse_proxy ( ) . await ;
48+ let client = Client :: builder ( TokioExecutor :: new ( ) ) . build_http ( ) ;
49+
50+ let req = build_request ( server_url, "grpc-web" , "grpc-web" ) ;
51+ let res = client. request ( req) . await . unwrap ( ) ;
52+ let content_type = res. headers ( ) . get ( header:: CONTENT_TYPE ) . unwrap ( ) . clone ( ) ;
53+ let content_type = content_type. to_str ( ) . unwrap ( ) ;
54+
55+ assert_eq ! ( res. status( ) , StatusCode :: OK ) ;
56+ assert_eq ! ( content_type, "application/grpc-web+proto" ) ;
57+
58+ let ( message, trailers) = decode_body ( res. into_body ( ) , content_type) . await ;
59+ let expected = Output {
60+ id : 1 ,
61+ desc : "one" . to_owned ( ) ,
62+ } ;
63+
64+ assert_eq ! ( message, expected) ;
65+ assert_eq ! ( & trailers[ ..] , b"grpc-status:0\r \n " ) ;
66+ }
67+
4568#[ tokio:: test]
4669async fn text_request ( ) {
4770 let server_url = spawn ( ) . await ;
@@ -84,6 +107,73 @@ async fn spawn() -> String {
84107 url
85108}
86109
110+ /// Spawn two servers, one serving the gRPC API and another acting as a grpc-web proxy
111+ async fn spawn_reverse_proxy ( ) -> String {
112+ use hyper_util:: rt:: TokioIo ;
113+ use hyper_util:: client:: legacy:: Client ;
114+ use tower_layer:: Layer ;
115+ use tower_service:: Service ;
116+
117+ // Set up gRPC service
118+ let addr = SocketAddr :: from ( ( [ 127 , 0 , 0 , 1 ] , 0 ) ) ;
119+ let listener = TcpListener :: bind ( addr) . await . expect ( "listener" ) ;
120+ let url = format ! ( "http://{}" , listener. local_addr( ) . unwrap( ) ) ;
121+ let listener_stream = TcpListenerStream :: new ( listener) ;
122+
123+ drop ( tokio:: spawn ( async move {
124+ Server :: builder ( )
125+ . add_service ( TestServer :: new ( Svc ) )
126+ . serve_with_incoming ( listener_stream)
127+ . await
128+ . unwrap ( )
129+ } ) ) ;
130+
131+ // Set up proxy to the above service that applies tonic-web
132+ let addr2 = SocketAddr :: from ( ( [ 127 , 0 , 0 , 1 ] , 0 ) ) ;
133+ let http_client = Client :: builder ( TokioExecutor :: new ( ) )
134+ . http2_only ( true )
135+ . build_http ( ) ;
136+ let listener2 = TcpListener :: bind ( addr2) . await . expect ( "listener" ) ;
137+ let url2 = format ! ( "http://{}" , listener2. local_addr( ) . unwrap( ) ) ;
138+
139+ let backend_url = url. clone ( ) ;
140+
141+ drop ( tokio:: spawn ( async move {
142+ loop {
143+ let ( stream, _) = listener2. accept ( ) . await . unwrap ( ) ;
144+ let io = TokioIo :: new ( stream) ;
145+ let client = http_client. clone ( ) ;
146+ let backend = backend_url. clone ( ) ;
147+
148+ tokio:: spawn ( async move {
149+ let svc = GrpcWebLayer :: new ( ) . layer ( client. clone ( ) ) ;
150+ let hyper_svc = hyper:: service:: service_fn ( move |mut req : Request < Incoming > | {
151+ let mut svc = svc. clone ( ) ;
152+ let backend = backend. clone ( ) ;
153+ async move {
154+ // Rewrite URI to point to backend
155+ let path = req. uri ( ) . path_and_query ( ) . map ( |pq| pq. as_str ( ) ) . unwrap_or ( "/" ) ;
156+ let new_uri = format ! ( "{}{}" , backend, path) . parse ( ) . unwrap ( ) ;
157+ * req. uri_mut ( ) = new_uri;
158+
159+ let req = req. map ( Body :: new) ;
160+ svc. call ( req) . await
161+ }
162+ } ) ;
163+
164+ if let Err ( err) = hyper_util:: server:: conn:: auto:: Builder :: new ( TokioExecutor :: new ( ) )
165+ . serve_connection ( io, hyper_svc)
166+ . await
167+ {
168+ eprintln ! ( "Error serving connection: {:?}" , err) ;
169+ }
170+ } ) ;
171+ }
172+ } ) ) ;
173+
174+ url2
175+ }
176+
87177fn encode_body ( ) -> Bytes {
88178 let input = Input {
89179 id : 1 ,
0 commit comments