xref: /tonic/tests/web/tests/grpc_web.rs (revision 60b131d2)
1 use std::net::SocketAddr;
2 
3 use base64::Engine as _;
4 use bytes::{Buf, BufMut, Bytes, BytesMut};
5 use http_body_util::{BodyExt as _, Full};
6 use hyper::body::Incoming;
7 use hyper::http::{header, StatusCode};
8 use hyper::{Method, Request, Uri};
9 use hyper_util::client::legacy::Client;
10 use hyper_util::rt::TokioExecutor;
11 use prost::Message;
12 use tokio::net::TcpListener;
13 use tokio_stream::wrappers::TcpListenerStream;
14 use tonic::body::Body;
15 use tonic::transport::Server;
16 
17 use test_web::pb::{test_server::TestServer, Input, Output};
18 use test_web::Svc;
19 use tonic::Status;
20 use tonic_web::GrpcWebLayer;
21 
22 #[tokio::test]
binary_request()23 async fn binary_request() {
24     let server_url = spawn().await;
25     let client = Client::builder(TokioExecutor::new()).build_http();
26 
27     let req = build_request(server_url, "grpc-web", "grpc-web");
28     let res = client.request(req).await.unwrap();
29     let content_type = res.headers().get(header::CONTENT_TYPE).unwrap().clone();
30     let content_type = content_type.to_str().unwrap();
31 
32     assert_eq!(res.status(), StatusCode::OK);
33     assert_eq!(content_type, "application/grpc-web+proto");
34 
35     let (message, trailers) = decode_body(res.into_body(), content_type).await;
36     let expected = Output {
37         id: 1,
38         desc: "one".to_owned(),
39     };
40 
41     assert_eq!(message, expected);
42     assert_eq!(&trailers[..], b"grpc-status:0\r\n");
43 }
44 
45 #[tokio::test]
text_request()46 async fn text_request() {
47     let server_url = spawn().await;
48     let client = Client::builder(TokioExecutor::new()).build_http();
49 
50     let req = build_request(server_url, "grpc-web-text", "grpc-web-text");
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-text+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 
spawn() -> String68 async fn spawn() -> String {
69     let addr = SocketAddr::from(([127, 0, 0, 1], 0));
70     let listener = TcpListener::bind(addr).await.expect("listener");
71     let url = format!("http://{}", listener.local_addr().unwrap());
72     let listener_stream = TcpListenerStream::new(listener);
73 
74     drop(tokio::spawn(async move {
75         Server::builder()
76             .accept_http1(true)
77             .layer(GrpcWebLayer::new())
78             .add_service(TestServer::new(Svc))
79             .serve_with_incoming(listener_stream)
80             .await
81             .unwrap()
82     }));
83 
84     url
85 }
86 
encode_body() -> Bytes87 fn encode_body() -> Bytes {
88     let input = Input {
89         id: 1,
90         desc: "one".to_owned(),
91     };
92 
93     let mut buf = BytesMut::with_capacity(1024);
94     buf.reserve(5);
95     unsafe {
96         buf.advance_mut(5);
97     }
98 
99     input.encode(&mut buf).unwrap();
100 
101     let len = buf.len() - 5;
102     {
103         let mut buf = &mut buf[..5];
104         buf.put_u8(0);
105         buf.put_u32(len as u32);
106     }
107 
108     buf.split_to(len + 5).freeze()
109 }
110 
build_request(base_uri: String, content_type: &str, accept: &str) -> Request<Body>111 fn build_request(base_uri: String, content_type: &str, accept: &str) -> Request<Body> {
112     use header::{ACCEPT, CONTENT_TYPE, ORIGIN};
113 
114     let request_uri = format!("{}/{}/{}", base_uri, "test.Test", "UnaryCall")
115         .parse::<Uri>()
116         .unwrap();
117 
118     let bytes = match content_type {
119         "grpc-web" => encode_body(),
120         "grpc-web-text" => test_web::util::base64::STANDARD
121             .encode(encode_body())
122             .into(),
123         _ => panic!("invalid content type {}", content_type),
124     };
125 
126     Request::builder()
127         .method(Method::POST)
128         .header(CONTENT_TYPE, format!("application/{}", content_type))
129         .header(ORIGIN, "http://example.com")
130         .header(ACCEPT, format!("application/{}", accept))
131         .uri(request_uri)
132         .body(Body::new(
133             Full::new(bytes).map_err(|err| Status::internal(err.to_string())),
134         ))
135         .unwrap()
136 }
137 
decode_body(body: Incoming, content_type: &str) -> (Output, Bytes)138 async fn decode_body(body: Incoming, content_type: &str) -> (Output, Bytes) {
139     let mut body = body.collect().await.unwrap().to_bytes();
140 
141     if content_type == "application/grpc-web-text+proto" {
142         body = test_web::util::base64::STANDARD
143             .decode(body)
144             .unwrap()
145             .into()
146     }
147 
148     body.advance(1);
149     let len = body.get_u32();
150     let msg = Output::decode(&mut body.split_to(len as usize)).expect("decode");
151     body.advance(5);
152 
153     (msg, body)
154 }
155