1use actix_web::{
4 error::ErrorInternalServerError,
5 http::{header, Method, StatusCode},
6 web, HttpRequest, HttpResponse,
7};
8use futures::TryStreamExt;
9use http::uri::PathAndQuery;
10use prost::Message;
11use prost_reflect::{DescriptorPool, DynamicMessage, Kind, MessageDescriptor, MethodDescriptor};
12use std::{collections::BTreeMap, fmt, sync::Arc};
13use tonic::{
14 client::Grpc,
15 codec::{Codec, DecodeBuf, Decoder, EncodeBuf, Encoder},
16 metadata::{AsciiMetadataKey, AsciiMetadataValue},
17 transport::Channel,
18 Code, Request, Status,
19};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum HttpVerb {
24 Get,
25 Put,
26 Post,
27 Delete,
28 Patch,
29}
30
31impl HttpVerb {
32 fn matches(self, method: &Method) -> bool {
33 matches!(
34 (self, method.as_str()),
35 (Self::Get, "GET")
36 | (Self::Put, "PUT")
37 | (Self::Post, "POST")
38 | (Self::Delete, "DELETE")
39 | (Self::Patch, "PATCH")
40 )
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct HttpBinding {
47 pub verb: HttpVerb,
48 pub path: String,
49 pub rpc: String,
50 pub body: Option<String>,
51}
52
53impl HttpBinding {
54 pub fn new(verb: HttpVerb, path: impl Into<String>, rpc: impl Into<String>) -> Self {
55 Self {
56 verb,
57 path: path.into(),
58 rpc: rpc.into(),
59 body: None,
60 }
61 }
62
63 pub fn with_body(mut self, field: impl Into<String>) -> Self {
65 self.body = Some(field.into());
66 self
67 }
68}
69
70#[derive(Clone)]
71struct Route {
72 binding: HttpBinding,
73 method: MethodDescriptor,
74}
75
76pub struct TranscoderBuilder {
78 pool: DescriptorPool,
79 channel: Channel,
80 bindings: Vec<HttpBinding>,
81}
82
83impl TranscoderBuilder {
84 pub fn from_descriptor_set(
85 bytes: impl AsRef<[u8]>,
86 channel: Channel,
87 ) -> Result<Self, TranscodeError> {
88 let pool = DescriptorPool::decode(bytes.as_ref()).map_err(TranscodeError::Descriptor)?;
89 Ok(Self {
90 pool,
91 channel,
92 bindings: Vec::new(),
93 })
94 }
95
96 pub async fn from_reflection(channel: Channel) -> Result<Self, TranscodeError> {
98 use tonic_reflection::pb::v1::{
99 server_reflection_client::ServerReflectionClient,
100 server_reflection_request::MessageRequest, server_reflection_response::MessageResponse,
101 ServerReflectionRequest,
102 };
103
104 async fn request(
105 client: &mut ServerReflectionClient<Channel>,
106 message_request: MessageRequest,
107 ) -> Result<MessageResponse, TranscodeError> {
108 let response = client
109 .server_reflection_info(futures::stream::iter([ServerReflectionRequest {
110 host: String::new(),
111 message_request: Some(message_request),
112 }]))
113 .await
114 .map_err(|status| TranscodeError::Status(Box::new(status)))?;
115 response
116 .into_inner()
117 .message()
118 .await
119 .map_err(|status| TranscodeError::Status(Box::new(status)))?
120 .and_then(|response| response.message_response)
121 .ok_or(TranscodeError::InvalidReflectionResponse)
122 }
123
124 let mut client = ServerReflectionClient::new(channel.clone());
125 let services =
126 match request(&mut client, MessageRequest::ListServices(String::new())).await? {
127 MessageResponse::ListServicesResponse(response) => response.service,
128 MessageResponse::ErrorResponse(error) => {
129 return Err(TranscodeError::Reflection(error.error_message));
130 }
131 _ => return Err(TranscodeError::InvalidReflectionResponse),
132 };
133 let mut files = BTreeMap::new();
134 for service in services {
135 if service.name.starts_with("grpc.reflection.") {
136 continue;
137 }
138 match request(
139 &mut client,
140 MessageRequest::FileContainingSymbol(service.name),
141 )
142 .await?
143 {
144 MessageResponse::FileDescriptorResponse(response) => {
145 for bytes in response.file_descriptor_proto {
146 let file = prost_types::FileDescriptorProto::decode(bytes.as_slice())
147 .map_err(TranscodeError::ReflectionDescriptor)?;
148 files.insert(file.name.clone().unwrap_or_default(), file);
149 }
150 }
151 MessageResponse::ErrorResponse(error) => {
152 return Err(TranscodeError::Reflection(error.error_message));
153 }
154 _ => return Err(TranscodeError::InvalidReflectionResponse),
155 }
156 }
157 let pool = DescriptorPool::from_file_descriptor_set(prost_types::FileDescriptorSet {
158 file: files.into_values().collect(),
159 })
160 .map_err(TranscodeError::Descriptor)?;
161 Ok(Self {
162 pool,
163 channel,
164 bindings: Vec::new(),
165 })
166 }
167
168 pub fn add_binding(mut self, binding: HttpBinding) -> Self {
169 self.bindings.push(binding);
170 self
171 }
172
173 pub fn load_annotated_bindings(mut self) -> Self {
176 let Some(extension) = self.pool.get_extension_by_name("google.api.http") else {
177 return self;
178 };
179 for service in self.pool.services() {
180 for method in service.methods() {
181 let options = method.options();
182 if !options.has_extension(&extension) {
183 continue;
184 }
185 let value = options.get_extension(&extension);
186 let Some(rule) = value.as_message() else {
187 continue;
188 };
189 collect_http_rules(rule, method.full_name(), &mut self.bindings);
190 }
191 }
192 self
193 }
194
195 pub fn build(self) -> Result<Transcoder, TranscodeError> {
196 let mut routes = Vec::with_capacity(self.bindings.len());
197 for binding in self.bindings {
198 validate_template(&binding.path)?;
199 let method = find_method(&self.pool, &binding.rpc)
200 .ok_or_else(|| TranscodeError::UnknownMethod(binding.rpc.clone()))?;
201 if method.is_client_streaming() {
202 return Err(TranscodeError::UnsupportedClientStreaming(binding.rpc));
203 }
204 routes.push(Route { binding, method });
205 }
206 routes.sort_by_key(|route| std::cmp::Reverse(literal_weight(&route.binding.path)));
207 Ok(Transcoder {
208 channel: self.channel,
209 routes: Arc::new(routes),
210 })
211 }
212}
213
214#[derive(Clone)]
216pub struct Transcoder {
217 channel: Channel,
218 routes: Arc<Vec<Route>>,
219}
220
221impl Transcoder {
222 pub async fn handle(&self, request: HttpRequest, body: web::Bytes) -> HttpResponse {
223 match self.invoke(&request, &body).await {
224 Ok(response) => response,
225 Err(TranscodeError::NoRoute) => HttpResponse::NotFound().json(error_json(
226 Code::NotFound,
227 "no HTTP-to-gRPC binding matched the request",
228 )),
229 Err(TranscodeError::InvalidJson(error)) => HttpResponse::BadRequest()
230 .json(error_json(Code::InvalidArgument, &error.to_string())),
231 Err(TranscodeError::Status(status)) => status_response(*status),
232 Err(error) => HttpResponse::InternalServerError()
233 .json(error_json(Code::Internal, &error.to_string())),
234 }
235 }
236
237 async fn invoke(
238 &self,
239 request: &HttpRequest,
240 body: &[u8],
241 ) -> Result<HttpResponse, TranscodeError> {
242 let (route, captures) = self
243 .routes
244 .iter()
245 .filter(|route| route.binding.verb.matches(request.method()))
246 .find_map(|route| {
247 match_template(&route.binding.path, request.path()).map(|c| (route, c))
248 })
249 .ok_or(TranscodeError::NoRoute)?;
250
251 let input = request_message(route, request, body, captures)?;
252 let mut grpc_request = Request::new(input);
253 forward_metadata(request, &mut grpc_request);
254 let path: PathAndQuery = format!(
255 "/{}/{}",
256 route.method.parent_service().full_name(),
257 route.method.name()
258 )
259 .parse()
260 .expect("protobuf method names form a valid gRPC path");
261 let codec = DynamicCodec::new(route.method.input(), route.method.output());
262 let mut grpc = Grpc::new(self.channel.clone());
263 grpc.ready().await.map_err(|error| {
264 TranscodeError::Status(Box::new(Status::unavailable(format!(
265 "gRPC transport unavailable: {error}"
266 ))))
267 })?;
268
269 if route.method.is_server_streaming() {
270 let response = grpc
271 .server_streaming(grpc_request, path, codec)
272 .await
273 .map_err(|status| TranscodeError::Status(Box::new(status)))?;
274 let metadata = response.metadata().clone();
275 let stream =
276 response
277 .into_inner()
278 .map_err(status_to_actix)
279 .and_then(|message| async move {
280 let mut json =
281 serde_json::to_vec(&message).map_err(ErrorInternalServerError)?;
282 json.push(b'\n');
283 Ok::<_, actix_web::Error>(web::Bytes::from(json))
284 });
285 let mut response = HttpResponse::Ok();
286 response.insert_header((header::CONTENT_TYPE, "application/json"));
287 copy_response_metadata(&metadata, &mut response);
288 return Ok(response.streaming(stream));
289 }
290
291 let response = grpc
292 .unary(grpc_request, path, codec)
293 .await
294 .map_err(|status| TranscodeError::Status(Box::new(status)))?;
295 let metadata = response.metadata().clone();
296 let json = serde_json::to_vec(response.get_ref()).map_err(TranscodeError::InvalidJson)?;
297 let mut downstream = HttpResponse::Ok();
298 downstream.insert_header((header::CONTENT_TYPE, "application/json"));
299 copy_response_metadata(&metadata, &mut downstream);
300 Ok(downstream.body(json))
301 }
302}
303
304pub async fn transcode(
306 transcoder: web::Data<Transcoder>,
307 request: HttpRequest,
308 body: web::Bytes,
309) -> HttpResponse {
310 transcoder.handle(request, body).await
311}
312
313fn request_message(
314 route: &Route,
315 request: &HttpRequest,
316 body: &[u8],
317 captures: BTreeMap<String, String>,
318) -> Result<DynamicMessage, TranscodeError> {
319 let mut value = if body.is_empty() {
320 serde_json::Value::Object(Default::default())
321 } else {
322 serde_json::from_slice(body).map_err(TranscodeError::InvalidJson)?
323 };
324 if let Some(field) = route.binding.body.as_deref() {
325 if field != "*" {
326 let mut root = serde_json::Map::new();
327 insert_json_path(&mut root, field, value);
328 value = serde_json::Value::Object(root);
329 }
330 } else if !body.is_empty() {
331 return Err(TranscodeError::UnexpectedBody);
332 }
333 let object = value
334 .as_object_mut()
335 .ok_or_else(|| TranscodeError::InvalidRequest("request JSON must be an object".into()))?;
336 for (name, raw) in captures {
337 insert_typed_value(object, &route.method.input(), &name, raw)?;
338 }
339 for pair in request
340 .query_string()
341 .split('&')
342 .filter(|pair| !pair.is_empty())
343 {
344 let (name, raw) = pair.split_once('=').unwrap_or((pair, ""));
345 let name = percent_decode(name)?;
346 let raw = percent_decode(raw)?;
347 insert_typed_value(object, &route.method.input(), &name, raw)?;
348 }
349 let serialized = serde_json::to_vec(&value).map_err(TranscodeError::InvalidJson)?;
350 let mut deserializer = serde_json::Deserializer::from_slice(&serialized);
351 DynamicMessage::deserialize(route.method.input(), &mut deserializer)
352 .map_err(TranscodeError::InvalidJson)
353}
354
355fn insert_typed_value(
356 object: &mut serde_json::Map<String, serde_json::Value>,
357 descriptor: &MessageDescriptor,
358 path: &str,
359 raw: String,
360) -> Result<(), TranscodeError> {
361 let top = path.split('.').next().unwrap_or(path);
362 let field = descriptor
363 .get_field_by_name(top)
364 .or_else(|| descriptor.fields().find(|field| field.json_name() == top))
365 .ok_or_else(|| TranscodeError::InvalidRequest(format!("unknown request field {path}")))?;
366 let value = match field.kind() {
367 Kind::Bool => serde_json::Value::Bool(raw.parse().map_err(|_| {
368 TranscodeError::InvalidRequest(format!("field {path} must be a boolean"))
369 })?),
370 Kind::Int32 | Kind::Sint32 | Kind::Sfixed32 => serde_json::Value::Number(
371 raw.parse::<i32>()
372 .map_err(|_| {
373 TranscodeError::InvalidRequest(format!("field {path} must be an integer"))
374 })?
375 .into(),
376 ),
377 Kind::Uint32 | Kind::Fixed32 => serde_json::Value::Number(
378 raw.parse::<u32>()
379 .map_err(|_| {
380 TranscodeError::InvalidRequest(format!(
381 "field {path} must be an unsigned integer"
382 ))
383 })?
384 .into(),
385 ),
386 _ => serde_json::Value::String(raw),
387 };
388 insert_json_path(object, path, value);
389 Ok(())
390}
391
392fn insert_json_path(
393 object: &mut serde_json::Map<String, serde_json::Value>,
394 path: &str,
395 value: serde_json::Value,
396) {
397 let mut parts = path.split('.').peekable();
398 let mut current = object;
399 while let Some(part) = parts.next() {
400 if parts.peek().is_none() {
401 current.insert(part.to_owned(), value);
402 return;
403 }
404 current = current
405 .entry(part)
406 .or_insert_with(|| serde_json::Value::Object(Default::default()))
407 .as_object_mut()
408 .expect("path collisions are rejected by protobuf JSON decoding");
409 }
410}
411
412fn forward_metadata(request: &HttpRequest, grpc: &mut Request<DynamicMessage>) {
413 for (name, value) in request.headers() {
414 let name = name.as_str();
415 if matches!(
416 name,
417 "host" | "content-type" | "content-length" | "connection" | "transfer-encoding"
418 ) || name.ends_with("-bin")
419 {
420 continue;
421 }
422 if let (Ok(key), Ok(value)) = (
423 name.parse::<AsciiMetadataKey>(),
424 AsciiMetadataValue::try_from(value.as_bytes()),
425 ) {
426 grpc.metadata_mut().append(key, value);
427 }
428 }
429}
430
431fn copy_response_metadata(
432 metadata: &tonic::metadata::MetadataMap,
433 response: &mut actix_web::HttpResponseBuilder,
434) {
435 for entry in metadata.iter() {
436 if let tonic::metadata::KeyAndValueRef::Ascii(key, value) = entry {
437 if let (Ok(name), Ok(value)) = (
438 header::HeaderName::try_from(key.as_str()),
439 header::HeaderValue::from_bytes(value.as_encoded_bytes()),
440 ) {
441 response.append_header((name, value));
442 }
443 }
444 }
445}
446
447fn collect_http_rules(rule: &DynamicMessage, rpc: &str, bindings: &mut Vec<HttpBinding>) {
448 let verb_and_path = [
449 ("get", HttpVerb::Get),
450 ("put", HttpVerb::Put),
451 ("post", HttpVerb::Post),
452 ("delete", HttpVerb::Delete),
453 ("patch", HttpVerb::Patch),
454 ]
455 .into_iter()
456 .find_map(|(field, verb)| {
457 rule.get_field_by_name(field).and_then(|value| {
458 value
459 .as_str()
460 .filter(|value| !value.is_empty())
461 .map(|path| (verb, path.to_owned()))
462 })
463 });
464 if let Some((verb, path)) = verb_and_path {
465 let body = rule.get_field_by_name("body").and_then(|value| {
466 value
467 .as_str()
468 .filter(|value| !value.is_empty())
469 .map(str::to_owned)
470 });
471 bindings.push(HttpBinding {
472 verb,
473 path,
474 rpc: rpc.to_owned(),
475 body,
476 });
477 }
478 let additional_bindings = rule.get_field_by_name("additional_bindings");
479 if let Some(rules) = additional_bindings
480 .as_deref()
481 .and_then(|value| value.as_list())
482 {
483 for rule in rules.iter().filter_map(|value| value.as_message()) {
484 collect_http_rules(rule, rpc, bindings);
485 }
486 }
487}
488
489fn find_method(pool: &DescriptorPool, name: &str) -> Option<MethodDescriptor> {
490 let normalized = name.trim_start_matches('/').replace('/', ".");
491 let (service, method) = normalized.rsplit_once('.')?;
492 pool.get_service_by_name(service)?
493 .methods()
494 .find(|candidate| candidate.name() == method)
495}
496
497fn validate_template(template: &str) -> Result<(), TranscodeError> {
498 if !template.starts_with('/')
499 || template
500 .split('/')
501 .any(|part| part.contains('{') != part.contains('}'))
502 {
503 return Err(TranscodeError::InvalidTemplate(template.to_owned()));
504 }
505 Ok(())
506}
507
508fn literal_weight(template: &str) -> usize {
509 template
510 .split('/')
511 .filter(|part| !part.starts_with('{'))
512 .map(str::len)
513 .sum()
514}
515
516fn match_template(template: &str, path: &str) -> Option<BTreeMap<String, String>> {
517 let template: Vec<_> = template.trim_matches('/').split('/').collect();
518 let path: Vec<_> = path.trim_matches('/').split('/').collect();
519 if template.len() != path.len() {
520 return None;
521 }
522 let mut captures = BTreeMap::new();
523 for (expected, actual) in template.into_iter().zip(path) {
524 if let Some(name) = expected
525 .strip_prefix('{')
526 .and_then(|part| part.strip_suffix('}'))
527 {
528 let name = name.split('=').next().unwrap_or(name);
529 captures.insert(name.to_owned(), percent_decode(actual).ok()?);
530 } else if expected != actual {
531 return None;
532 }
533 }
534 Some(captures)
535}
536
537fn percent_decode(value: &str) -> Result<String, TranscodeError> {
538 let mut bytes = Vec::with_capacity(value.len());
539 let input = value.as_bytes();
540 let mut index = 0;
541 while index < input.len() {
542 match input[index] {
543 b'%' if index + 2 < input.len() => {
544 let hex = std::str::from_utf8(&input[index + 1..index + 3]).map_err(|_| {
545 TranscodeError::InvalidRequest("invalid percent encoding".into())
546 })?;
547 bytes.push(u8::from_str_radix(hex, 16).map_err(|_| {
548 TranscodeError::InvalidRequest("invalid percent encoding".into())
549 })?);
550 index += 3;
551 }
552 b'+' => {
553 bytes.push(b' ');
554 index += 1;
555 }
556 byte => {
557 bytes.push(byte);
558 index += 1;
559 }
560 }
561 }
562 String::from_utf8(bytes)
563 .map_err(|_| TranscodeError::InvalidRequest("path/query value is not UTF-8".into()))
564}
565
566pub fn grpc_status_to_http(code: Code) -> StatusCode {
568 match code {
569 Code::Ok => StatusCode::OK,
570 Code::Cancelled => StatusCode::from_u16(499).expect("499 is a valid extension status"),
571 Code::Unknown | Code::Internal | Code::DataLoss => StatusCode::INTERNAL_SERVER_ERROR,
572 Code::InvalidArgument | Code::FailedPrecondition | Code::OutOfRange => {
573 StatusCode::BAD_REQUEST
574 }
575 Code::DeadlineExceeded => StatusCode::GATEWAY_TIMEOUT,
576 Code::NotFound => StatusCode::NOT_FOUND,
577 Code::AlreadyExists | Code::Aborted => StatusCode::CONFLICT,
578 Code::PermissionDenied => StatusCode::FORBIDDEN,
579 Code::Unauthenticated => StatusCode::UNAUTHORIZED,
580 Code::ResourceExhausted => StatusCode::TOO_MANY_REQUESTS,
581 Code::Unimplemented => StatusCode::NOT_IMPLEMENTED,
582 Code::Unavailable => StatusCode::SERVICE_UNAVAILABLE,
583 }
584}
585
586fn status_response(status: Status) -> HttpResponse {
587 HttpResponse::build(grpc_status_to_http(status.code()))
588 .json(error_json(status.code(), status.message()))
589}
590
591fn error_json(code: Code, message: &str) -> serde_json::Value {
592 serde_json::json!({
593 "code": code as i32,
594 "status": format!("{code:?}").to_ascii_uppercase(),
595 "message": message,
596 })
597}
598
599fn status_to_actix(status: Status) -> actix_web::Error {
600 ErrorInternalServerError(status.to_string())
601}
602
603#[derive(Clone)]
604struct DynamicCodec {
605 output: MessageDescriptor,
606}
607
608impl DynamicCodec {
609 fn new(_input: MessageDescriptor, output: MessageDescriptor) -> Self {
610 Self { output }
611 }
612}
613
614impl Codec for DynamicCodec {
615 type Encode = DynamicMessage;
616 type Decode = DynamicMessage;
617 type Encoder = DynamicEncoder;
618 type Decoder = DynamicDecoder;
619
620 fn encoder(&mut self) -> Self::Encoder {
621 DynamicEncoder
622 }
623
624 fn decoder(&mut self) -> Self::Decoder {
625 DynamicDecoder(self.output.clone())
626 }
627}
628
629struct DynamicEncoder;
630
631impl Encoder for DynamicEncoder {
632 type Item = DynamicMessage;
633 type Error = Status;
634
635 fn encode(&mut self, item: Self::Item, dst: &mut EncodeBuf<'_>) -> Result<(), Self::Error> {
636 item.encode(dst).map_err(|error| {
637 Status::internal(format!("failed to encode protobuf request: {error}"))
638 })
639 }
640}
641
642struct DynamicDecoder(MessageDescriptor);
643
644impl Decoder for DynamicDecoder {
645 type Item = DynamicMessage;
646 type Error = Status;
647
648 fn decode(&mut self, src: &mut DecodeBuf<'_>) -> Result<Option<Self::Item>, Self::Error> {
649 DynamicMessage::decode(self.0.clone(), src)
650 .map(Some)
651 .map_err(|error| {
652 Status::internal(format!("failed to decode protobuf response: {error}"))
653 })
654 }
655}
656
657#[derive(Debug)]
659pub enum TranscodeError {
660 Descriptor(prost_reflect::DescriptorError),
661 ReflectionDescriptor(prost::DecodeError),
662 Reflection(String),
663 InvalidReflectionResponse,
664 UnknownMethod(String),
665 UnsupportedClientStreaming(String),
666 InvalidTemplate(String),
667 NoRoute,
668 UnexpectedBody,
669 InvalidRequest(String),
670 InvalidJson(serde_json::Error),
671 Status(Box<Status>),
672}
673
674impl fmt::Display for TranscodeError {
675 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
676 match self {
677 Self::Descriptor(error) => {
678 write!(formatter, "invalid protobuf descriptor set: {error}")
679 }
680 Self::ReflectionDescriptor(error) => {
681 write!(formatter, "invalid reflected protobuf descriptor: {error}")
682 }
683 Self::Reflection(message) => write!(formatter, "gRPC reflection failed: {message}"),
684 Self::InvalidReflectionResponse => {
685 formatter.write_str("gRPC reflection returned an unexpected response")
686 }
687 Self::UnknownMethod(method) => write!(formatter, "unknown protobuf method: {method}"),
688 Self::UnsupportedClientStreaming(method) => write!(
689 formatter,
690 "HTTP transcoding does not support client-streaming method {method}"
691 ),
692 Self::InvalidTemplate(path) => write!(formatter, "invalid HTTP path template: {path}"),
693 Self::NoRoute => formatter.write_str("no HTTP binding matched"),
694 Self::UnexpectedBody => {
695 formatter.write_str("this HTTP binding does not accept a request body")
696 }
697 Self::InvalidRequest(message) => formatter.write_str(message),
698 Self::InvalidJson(error) => write!(formatter, "invalid protobuf JSON: {error}"),
699 Self::Status(status) => write!(formatter, "gRPC request failed: {status}"),
700 }
701 }
702}
703
704impl std::error::Error for TranscodeError {}
705
706#[cfg(test)]
707mod tests {
708 use super::*;
709 use actix_web::{test, App};
710 use std::time::Duration;
711 use tokio::net::TcpListener;
712 use tokio_stream::wrappers::TcpListenerStream;
713 use tonic::{transport::Server, Response};
714
715 mod fixture {
716 tonic::include_proto!("rust_zero.gateway_test");
717 }
718
719 use fixture::{
720 greeter_server::{Greeter, GreeterServer},
721 GetRequest, GetResponse,
722 };
723
724 #[derive(Default)]
725 struct GreeterService;
726
727 #[tonic::async_trait]
728 impl Greeter for GreeterService {
729 type WatchStream = tokio_stream::wrappers::ReceiverStream<Result<GetResponse, Status>>;
730
731 async fn get(&self, request: Request<GetRequest>) -> Result<Response<GetResponse>, Status> {
732 let request = request.into_inner();
733 let mut response = Response::new(GetResponse {
734 id: request.id,
735 message: request.view,
736 });
737 response
738 .metadata_mut()
739 .insert("x-backend", "grpc".parse().unwrap());
740 Ok(response)
741 }
742
743 async fn watch(
744 &self,
745 request: Request<GetRequest>,
746 ) -> Result<Response<Self::WatchStream>, Status> {
747 let request = request.into_inner();
748 let (sender, receiver) = tokio::sync::mpsc::channel(2);
749 sender
750 .send(Ok(GetResponse {
751 id: request.id,
752 message: "one".into(),
753 }))
754 .await
755 .unwrap();
756 sender
757 .send(Ok(GetResponse {
758 id: request.id,
759 message: "two".into(),
760 }))
761 .await
762 .unwrap();
763 Ok(Response::new(tokio_stream::wrappers::ReceiverStream::new(
764 receiver,
765 )))
766 }
767
768 async fn fail(&self, _: Request<GetRequest>) -> Result<Response<GetResponse>, Status> {
769 Err(Status::not_found("missing greeter"))
770 }
771 }
772
773 async fn fixture() -> (Transcoder, tokio::task::JoinHandle<()>) {
774 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
775 let address = listener.local_addr().unwrap();
776 let reflection = tonic_reflection::server::Builder::configure()
777 .register_encoded_file_descriptor_set(include_bytes!(concat!(
778 env!("OUT_DIR"),
779 "/gateway.bin"
780 )))
781 .build_v1()
782 .unwrap();
783 let server = tokio::spawn(async move {
784 Server::builder()
785 .add_service(reflection)
786 .add_service(GreeterServer::new(GreeterService))
787 .serve_with_incoming(TcpListenerStream::new(listener))
788 .await
789 .unwrap();
790 });
791 let channel = tonic::transport::Endpoint::from_shared(format!("http://{address}"))
792 .unwrap()
793 .connect_timeout(Duration::from_secs(1))
794 .connect()
795 .await
796 .unwrap();
797 let transcoder = TranscoderBuilder::from_descriptor_set(
798 include_bytes!(concat!(env!("OUT_DIR"), "/gateway.bin")),
799 channel,
800 )
801 .unwrap()
802 .add_binding(HttpBinding::new(
803 HttpVerb::Get,
804 "/v1/greeters/{id}",
805 "rust_zero.gateway_test.Greeter.Get",
806 ))
807 .add_binding(HttpBinding::new(
808 HttpVerb::Get,
809 "/v1/greeters/{id}/watch",
810 "rust_zero.gateway_test.Greeter.Watch",
811 ))
812 .add_binding(HttpBinding::new(
813 HttpVerb::Get,
814 "/v1/missing/{id}",
815 "rust_zero.gateway_test.Greeter.Fail",
816 ))
817 .build()
818 .unwrap();
819 (transcoder, server)
820 }
821
822 #[actix_web::test]
823 async fn loads_descriptors_from_live_grpc_reflection() {
824 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
825 let address = listener.local_addr().unwrap();
826 let reflection = tonic_reflection::server::Builder::configure()
827 .register_encoded_file_descriptor_set(include_bytes!(concat!(
828 env!("OUT_DIR"),
829 "/gateway.bin"
830 )))
831 .build_v1()
832 .unwrap();
833 let server = tokio::spawn(async move {
834 Server::builder()
835 .add_service(reflection)
836 .add_service(GreeterServer::new(GreeterService))
837 .serve_with_incoming(TcpListenerStream::new(listener))
838 .await
839 .unwrap();
840 });
841 let channel = tonic::transport::Endpoint::from_shared(format!("http://{address}"))
842 .unwrap()
843 .connect()
844 .await
845 .unwrap();
846 let transcoder = TranscoderBuilder::from_reflection(channel)
847 .await
848 .unwrap()
849 .add_binding(HttpBinding::new(
850 HttpVerb::Get,
851 "/v1/reflected/{id}",
852 "rust_zero.gateway_test.Greeter.Get",
853 ))
854 .build()
855 .unwrap();
856 let app = test::init_service(
857 App::new()
858 .app_data(web::Data::new(transcoder))
859 .default_service(web::to(transcode)),
860 )
861 .await;
862 let response = test::call_service(
863 &app,
864 test::TestRequest::get()
865 .uri("/v1/reflected/11?view=reflection")
866 .to_request(),
867 )
868 .await;
869 assert_eq!(response.status(), StatusCode::OK);
870 server.abort();
871 }
872
873 #[actix_web::test]
874 async fn transcodes_path_query_metadata_and_protobuf_json() {
875 let (transcoder, server) = fixture().await;
876 let app = test::init_service(
877 App::new()
878 .app_data(web::Data::new(transcoder))
879 .default_service(web::to(transcode)),
880 )
881 .await;
882 let response = test::call_service(
883 &app,
884 test::TestRequest::get()
885 .uri("/v1/greeters/7?view=full")
886 .insert_header(("x-request-id", "request-1"))
887 .to_request(),
888 )
889 .await;
890 assert_eq!(response.status(), StatusCode::OK);
891 assert_eq!(response.headers().get("x-backend").unwrap(), "grpc");
892 let body: serde_json::Value = test::read_body_json(response).await;
893 assert_eq!(body, serde_json::json!({"id": 7, "message": "full"}));
894 server.abort();
895 }
896
897 #[actix_web::test]
898 async fn streams_newline_delimited_json_and_maps_grpc_statuses() {
899 let (transcoder, server) = fixture().await;
900 let app = test::init_service(
901 App::new()
902 .app_data(web::Data::new(transcoder))
903 .default_service(web::to(transcode)),
904 )
905 .await;
906 let response = test::call_service(
907 &app,
908 test::TestRequest::get()
909 .uri("/v1/greeters/9/watch")
910 .to_request(),
911 )
912 .await;
913 assert_eq!(response.status(), StatusCode::OK);
914 assert_eq!(
915 test::read_body(response).await,
916 "{\"id\":9,\"message\":\"one\"}\n{\"id\":9,\"message\":\"two\"}\n"
917 );
918
919 let response = test::call_service(
920 &app,
921 test::TestRequest::get().uri("/v1/missing/9").to_request(),
922 )
923 .await;
924 assert_eq!(response.status(), StatusCode::NOT_FOUND);
925 let body: serde_json::Value = test::read_body_json(response).await;
926 assert_eq!(body["message"], "missing greeter");
927 server.abort();
928 }
929}