1use std::collections::HashMap as StdHashMap;
2use std::path::PathBuf;
3use std::sync::Arc;
4use std::sync::OnceLock;
5use std::sync::atomic::{AtomicU64, Ordering};
6
7use async_trait::async_trait;
8use base64::Engine;
9use bytes::BytesMut;
10use camel_api::security_policy::AuthPrincipal;
11use camel_api::store_principal_properties;
12use camel_api::{Body, CamelError, Exchange, Message, Value};
13use camel_auth::{AuthenticatedPrincipal, CredentialSource, enforce_dispatch, install_carrier};
14use camel_component_api::{
15 ConcurrencyModel, Consumer, ConsumerContext, ConsumerStartupMode, ExchangeEnvelope,
16 SecurityContext,
17};
18use camel_proto_compiler::ProtoCache;
19use prost::Message as _;
20use prost_reflect::{DynamicMessage, MessageDescriptor};
21use tokio::sync::mpsc;
22use tonic::Status;
23use tracing::{debug, info};
24
25use crate::config::GrpcServerConfig;
26use crate::mode::GrpcMode;
27use crate::server::GrpcDispatchTable;
28use crate::server::GrpcKernelAuth;
29use crate::server::GrpcServerRegistry;
30
31static PROTO_CACHE: OnceLock<ProtoCache> = OnceLock::new();
32
33pub(crate) struct KernelRequestAuth {
37 plan: camel_api::security_policy::RouteSecurityPlan,
38 principal: AuthenticatedPrincipal,
39}
40
41impl KernelRequestAuth {
42 fn apply_to(&self, exchange: &mut Exchange) -> Result<(), Status> {
51 store_principal_properties(exchange, self.principal.principal());
52 install_carrier(exchange, &self.principal);
53 enforce_dispatch(&self.plan, exchange).map_err(|e| match e {
54 CamelError::Unauthenticated(msg) => Status::unauthenticated(msg),
55 other => Status::internal(other.to_string()),
56 })
57 }
58}
59
60fn kernel_request_auth(
66 kernel: Option<&GrpcKernelAuth>,
67 principal: Option<AuthenticatedPrincipal>,
68) -> Option<KernelRequestAuth> {
69 let kernel = kernel?;
70 let principal = principal?;
71 Some(KernelRequestAuth {
72 plan: kernel.plan.clone(),
73 principal,
74 })
75}
76
77fn proto_cache() -> &'static ProtoCache {
78 PROTO_CACHE.get_or_init(ProtoCache::new)
79}
80
81fn pipeline_error_to_status(e: CamelError) -> Status {
90 match e {
91 CamelError::Unauthorized(msg) => Status::permission_denied(msg),
92 other => Status::internal(format!("pipeline error: {other}")),
93 }
94}
95
96pub fn resolve_grpc_mode(
98 proto_path: &PathBuf,
99 service_name: &str,
100 method_name: &str,
101) -> Result<GrpcMode, CamelError> {
102 let cache = proto_cache();
103 let pool = cache
104 .get_or_compile(proto_path, std::iter::empty::<&std::path::Path>())
105 .map_err(|e| CamelError::EndpointCreationFailed(format!("failed to compile proto: {e}")))?;
106
107 let svc = pool.get_service_by_name(service_name).ok_or_else(|| {
108 CamelError::EndpointCreationFailed(format!(
109 "service descriptor not found: {}",
110 service_name
111 ))
112 })?;
113
114 let method = svc
115 .methods()
116 .find(|m| m.name() == method_name)
117 .ok_or_else(|| {
118 CamelError::EndpointCreationFailed(format!(
119 "method descriptor not found: {}/{}",
120 service_name, method_name
121 ))
122 })?;
123
124 Ok(GrpcMode::from_method(&method))
125}
126
127const RESERVED_METADATA_KEYS: &[&str] = &[
128 "content-type",
129 "te",
130 "grpc-encoding",
131 "grpc-accept-encoding",
132 "grpc-status",
133 "grpc-message",
134 "grpc-status-details-bin",
135 "user-agent",
136];
137
138fn extract_metadata(metadata: &tonic::metadata::MetadataMap) -> Vec<(String, serde_json::Value)> {
139 let mut headers = Vec::new();
140 for key_and_value in metadata.iter() {
141 use tonic::metadata::KeyAndValueRef;
142 match key_and_value {
143 KeyAndValueRef::Ascii(key, value) => {
144 let key_str = key.as_str();
145 if RESERVED_METADATA_KEYS.contains(&key_str) {
146 continue;
147 }
148 if let Ok(v) = value.to_str() {
149 headers.push((
150 key_str.to_string(),
151 serde_json::Value::String(v.to_string()),
152 ));
153 }
154 }
155 KeyAndValueRef::Binary(key, value) => {
156 let key_str = key.as_str();
157 if RESERVED_METADATA_KEYS.contains(&key_str) {
158 continue;
159 }
160 let encoded = base64::engine::general_purpose::STANDARD.encode(value);
161 headers.push((format!("bin:{key_str}"), serde_json::Value::String(encoded)));
162 }
163 }
164 }
165 headers
166}
167
168pub(crate) enum GrpcStreamItem {
169 Message(Vec<u8>),
170 Error(tonic::Status),
171 Done,
172}
173
174pub(crate) enum GrpcReply {
175 Ok(Vec<u8>),
176 Err(tonic::Status),
177}
178
179pub(crate) enum GrpcRequestEnvelope {
189 Unary {
190 metadata: tonic::metadata::MetadataMap,
191 body: Vec<u8>,
192 reply_tx: tokio::sync::oneshot::Sender<GrpcReply>,
193 kernel_principal: Option<AuthenticatedPrincipal>,
194 },
195 ServerStreaming {
196 metadata: tonic::metadata::MetadataMap,
197 body: Vec<u8>,
198 reply_tx: mpsc::Sender<GrpcStreamItem>,
199 kernel_principal: Option<AuthenticatedPrincipal>,
200 },
201 ClientStreaming {
202 metadata: tonic::metadata::MetadataMap,
203 body_rx: mpsc::Receiver<Vec<u8>>,
204 reply_tx: tokio::sync::oneshot::Sender<GrpcReply>,
205 kernel_principal: Option<AuthenticatedPrincipal>,
206 },
207 Bidi {
208 metadata: tonic::metadata::MetadataMap,
209 body_rx: mpsc::Receiver<Vec<u8>>,
210 reply_tx: mpsc::Sender<GrpcStreamItem>,
211 kernel_principal: Option<AuthenticatedPrincipal>,
212 },
213}
214
215static OBSERVER_REGISTRY: OnceLock<std::sync::Mutex<StdHashMap<String, GrpcStreamObserver>>> =
218 OnceLock::new();
219
220static OBSERVER_COUNTER: AtomicU64 = AtomicU64::new(0);
221
222fn next_observer_id() -> String {
223 let n = OBSERVER_COUNTER.fetch_add(1, Ordering::Relaxed);
224 format!("obs-{n}")
225}
226
227fn observer_registry() -> &'static std::sync::Mutex<StdHashMap<String, GrpcStreamObserver>> {
228 OBSERVER_REGISTRY.get_or_init(|| std::sync::Mutex::new(StdHashMap::new()))
229}
230
231fn register_observer(id: String, observer: GrpcStreamObserver) {
232 let registry = observer_registry();
233 let mut registry = match registry.lock() {
234 Ok(g) => g,
235 Err(poisoned) => poisoned.into_inner(),
236 };
237 registry.insert(id, observer);
238}
239
240fn remove_observer(id: &str) -> Option<GrpcStreamObserver> {
241 let registry = observer_registry();
242 let mut registry = match registry.lock() {
243 Ok(g) => g,
244 Err(poisoned) => poisoned.into_inner(),
245 };
246 registry.remove(id)
247}
248
249pub fn take_stream_observer(exchange: &Exchange) -> Option<GrpcStreamObserver> {
250 let id = exchange
251 .properties
252 .get("CamelGrpcStreamObserverId")?
253 .as_str()?;
254 remove_observer(id)
255}
256
257struct ObserverGuard {
260 id: String,
261}
262
263impl ObserverGuard {
264 fn new(id: String) -> Self {
265 Self { id }
266 }
267}
268
269impl Drop for ObserverGuard {
270 fn drop(&mut self) {
271 remove_observer(&self.id);
272 }
273}
274
275#[derive(Clone)]
278pub struct GrpcStreamObserver {
279 tx: mpsc::Sender<GrpcStreamItem>,
280 resp_desc: MessageDescriptor,
281}
282
283impl GrpcStreamObserver {
284 pub(crate) fn new(tx: mpsc::Sender<GrpcStreamItem>, resp_desc: MessageDescriptor) -> Self {
285 Self { tx, resp_desc }
286 }
287
288 pub async fn on_next(&self, json: serde_json::Value) -> Result<(), CamelError> {
289 let encoded = json_to_protobuf_bytes(json, self.resp_desc.clone())
290 .map_err(|e| CamelError::ProcessorError(format!("failed to encode protobuf: {e}")))?;
291 self.tx
292 .send(GrpcStreamItem::Message(encoded))
293 .await
294 .map_err(|_| CamelError::ProcessorError("stream observer channel closed".into()))
295 }
296
297 pub async fn on_error(&self, status: Status) {
298 if self.tx.send(GrpcStreamItem::Error(status)).await.is_err() {
299 tracing::debug!("grpc stream observer: failed to send error, channel closed");
300 }
301 }
302
303 pub async fn on_completed(&self) {
304 if self.tx.send(GrpcStreamItem::Done).await.is_err() {
305 tracing::debug!("grpc stream observer: failed to send done, channel closed");
306 }
307 }
308}
309
310fn json_to_protobuf_bytes(
313 json: serde_json::Value,
314 desc: MessageDescriptor,
315) -> Result<Vec<u8>, Status> {
316 let json_str = serde_json::to_string(&json)
317 .map_err(|e| Status::internal(format!("failed to serialize JSON: {e}")))?;
318 let mut de = serde_json::Deserializer::from_str(&json_str);
319 let resp_dyn = DynamicMessage::deserialize(desc, &mut de)
320 .map_err(|e| Status::internal(format!("failed to parse JSON into protobuf: {e}")))?;
321 let mut buf = BytesMut::new();
322 prost::Message::encode(&resp_dyn, &mut buf)
323 .map_err(|e| Status::internal(format!("failed to encode protobuf: {e}")))?;
324 Ok(buf.to_vec())
325}
326
327pub(crate) fn validate_credential_sources(sources: &[CredentialSource]) -> Result<(), CamelError> {
337 for source in sources {
338 let source_kind = match source {
339 CredentialSource::QueryParam { .. } => "query_param",
340 CredentialSource::Cookie { .. } => "cookie",
341 _ => continue,
342 };
343 return Err(CamelError::Config(format!(
344 "grpc routes cannot carry {source_kind} credential sources; supported: authorization_header, header" )));
346 }
347 Ok(())
348}
349
350pub struct GrpcConsumer {
351 host: String,
352 port: u16,
353 path: String,
354 proto_path: PathBuf,
355 service_name: String,
356 method_name: String,
357 mode: GrpcMode,
358 security_ctx: Option<SecurityContext>,
359 runtime: Arc<dyn camel_component_api::RuntimeObservability>,
360 server_config: GrpcServerConfig,
361}
362
363impl GrpcConsumer {
364 #[allow(clippy::too_many_arguments)]
365 pub fn new(
366 host: String,
367 port: u16,
368 path: String,
369 proto_path: PathBuf,
370 service_name: String,
371 method_name: String,
372 mode: GrpcMode,
373 runtime: Arc<dyn camel_component_api::RuntimeObservability>,
374 server_config: GrpcServerConfig,
375 ) -> Self {
376 Self {
377 host,
378 port,
379 path,
380 proto_path,
381 service_name,
382 method_name,
383 mode,
384 security_ctx: None,
385 runtime,
386 server_config,
387 }
388 }
389
390 fn resolve_descriptors(&self) -> Result<(MessageDescriptor, MessageDescriptor), CamelError> {
391 let cache = proto_cache();
392 let pool = cache
393 .get_or_compile(&self.proto_path, std::iter::empty::<&std::path::Path>())
394 .map_err(|e| {
395 CamelError::EndpointCreationFailed(format!("failed to compile proto: {e}"))
396 })?;
397
398 let svc = pool
399 .get_service_by_name(&self.service_name)
400 .ok_or_else(|| {
401 CamelError::EndpointCreationFailed(format!(
402 "service descriptor not found: {}",
403 self.service_name
404 ))
405 })?;
406
407 let method = svc
408 .methods()
409 .find(|m| m.name() == self.method_name)
410 .ok_or_else(|| {
411 CamelError::EndpointCreationFailed(format!(
412 "method descriptor not found: {}/{}",
413 self.service_name, self.method_name
414 ))
415 })?;
416
417 Ok((method.input(), method.output()))
418 }
419
420 fn validate_route_credential_sources(&self) -> Result<(), CamelError> {
424 let Some(sec_ctx) = &self.security_ctx else {
425 return Ok(());
426 };
427 validate_credential_sources(&sec_ctx.credential_sources)?;
428 if let Some(plan) = &sec_ctx.plan {
429 validate_credential_sources(&plan.credential_sources)?;
430 }
431 Ok(())
432 }
433
434 pub async fn start_with_listener(
435 &mut self,
436 ctx: ConsumerContext,
437 listener: tokio::net::TcpListener,
438 ) -> Result<(), CamelError> {
439 self.validate_route_credential_sources()?;
440 let dispatch = GrpcServerRegistry::global()
441 .get_or_spawn_with_listener(
442 listener,
443 &self.host,
444 self.port,
445 self.server_config.clone(),
446 Arc::clone(&self.runtime),
447 )
448 .await?;
449 self.start_inner(ctx, dispatch).await
450 }
451
452 async fn start_inner(
453 &mut self,
454 ctx: ConsumerContext,
455 dispatch: GrpcDispatchTable,
456 ) -> Result<(), CamelError> {
457 let (req_desc, resp_desc) = self.resolve_descriptors()?;
458 let mode = self.mode;
459
460 let (env_tx, mut env_rx) = mpsc::channel::<GrpcRequestEnvelope>(64);
461 let kernel = self
467 .security_ctx
468 .as_ref()
469 .and_then(GrpcKernelAuth::from_security_context)
470 .map(Arc::new);
471 {
472 let mut table = dispatch.write().await;
473 if table.contains_key(&self.path) {
474 return Err(CamelError::EndpointCreationFailed(format!(
475 "duplicate gRPC consumer path: {}",
476 self.path
477 )));
478 }
479 table.insert(self.path.clone(), (env_tx, mode, kernel.clone()));
480 }
481
482 let path = self.path.clone();
483 let host = self.host.clone();
484 let port = self.port;
485 let sender = ctx.sender();
486
487 info!(
488 path = %path,
489 host = %host,
490 port = port,
491 mode = ?mode,
492 "grpc consumer started, waiting for requests"
493 );
494
495 let semaphore = std::sync::Arc::new(tokio::sync::Semaphore::new(64));
498 let mut join_set = tokio::task::JoinSet::new();
499
500 loop {
501 tokio::select! {
502 biased;
503 _ = ctx.cancelled() => {
504 info!(
505 path = %path,
506 "grpc consumer cancelled, shutting down"
507 );
508 break;
509 }
510 envelope = env_rx.recv() => {
511 let Some(envelope) = envelope else { break };
512
513 let sem = semaphore.clone();
514 let permit = sem.acquire_owned().await.map_err(|_| CamelError::ChannelClosed)?;
515 let req_desc = req_desc.clone();
516 let resp_desc = resp_desc.clone();
517 let sender = sender.clone();
518 let correlation_id = next_observer_id();
519 let path_for_log = path.clone();
520 let kernel = kernel.clone();
527
528 debug!(
529 path = %path_for_log,
530 correlation_id = %correlation_id,
531 "grpc consumer received request"
532 );
533
534 join_set.spawn(async move {
535 let _permit = permit;
536 match envelope {
537 GrpcRequestEnvelope::Unary { metadata, body, reply_tx, kernel_principal } => {
538 debug!(
539 path = %path_for_log,
540 correlation_id = %correlation_id,
541 size = body.len(),
542 "grpc consumer processing unary request"
543 );
544
545 let kernel_auth = kernel_request_auth(kernel.as_deref(), kernel_principal);
546 let result = process_unary_request(
547 body, metadata, req_desc, resp_desc, sender, kernel_auth,
548 ).await;
549 let reply = match result {
550 Ok(bytes) => GrpcReply::Ok(bytes),
551 Err(status) => GrpcReply::Err(status),
552 };
553 let _ = reply_tx.send(reply);
554 }
555 GrpcRequestEnvelope::ServerStreaming { metadata, body, reply_tx, kernel_principal } => {
556 debug!(
557 path = %path_for_log,
558 correlation_id = %correlation_id,
559 size = body.len(),
560 "grpc consumer processing server streaming request"
561 );
562
563 let kernel_auth = kernel_request_auth(kernel.as_deref(), kernel_principal);
564 process_server_streaming_request(
565 body, metadata, req_desc, resp_desc, sender, reply_tx, kernel_auth,
566 ).await;
567 }
568 GrpcRequestEnvelope::ClientStreaming { metadata, body_rx, reply_tx, kernel_principal } => {
569 debug!(
570 path = %path_for_log,
571 correlation_id = %correlation_id,
572 "grpc consumer processing client streaming request"
573 );
574
575 let kernel_auth = kernel_request_auth(kernel.as_deref(), kernel_principal);
576 process_client_streaming_request(
577 body_rx, metadata, req_desc, resp_desc, sender, reply_tx, kernel_auth,
578 ).await;
579 }
580 GrpcRequestEnvelope::Bidi { metadata, body_rx, reply_tx, kernel_principal } => {
581 debug!(
582 path = %path_for_log,
583 correlation_id = %correlation_id,
584 "grpc consumer processing bidi streaming request"
585 );
586
587 let kernel_auth = kernel_request_auth(kernel.as_deref(), kernel_principal);
588 process_bidi_request(
589 body_rx, metadata, req_desc, resp_desc, sender, reply_tx, kernel_auth,
590 ).await;
591 }
592 }
593 });
594 }
595 }
596 }
597
598 join_set.shutdown().await;
599
600 GrpcServerRegistry::global()
601 .unregister(&host, port, &path)
602 .await;
603
604 info!(
605 path = %path,
606 "grpc consumer stopped"
607 );
608
609 Ok(())
610 }
611}
612
613#[async_trait]
614impl Consumer for GrpcConsumer {
615 async fn start(&mut self, ctx: ConsumerContext) -> Result<(), CamelError> {
616 self.validate_route_credential_sources()?;
617 info!(
618 host = %self.host,
619 port = self.port,
620 service = %self.service_name,
621 method = %self.method_name,
622 mode = ?self.mode,
623 "grpc consumer starting"
624 );
625 let dispatch = GrpcServerRegistry::global()
626 .get_or_spawn(
627 &self.host,
628 self.port,
629 self.server_config.clone(),
630 Arc::clone(&self.runtime),
631 )
632 .await?;
633 ctx.mark_ready();
636 self.start_inner(ctx, dispatch).await
637 }
638
639 async fn stop(&mut self) -> Result<(), CamelError> {
640 info!(
641 host = %self.host,
642 port = self.port,
643 service = %self.service_name,
644 method = %self.method_name,
645 "grpc consumer stopping"
646 );
647 GrpcServerRegistry::global()
648 .unregister(&self.host, self.port, &self.path)
649 .await;
650 Ok(())
651 }
652
653 fn concurrency_model(&self) -> ConcurrencyModel {
654 ConcurrencyModel::Concurrent { max: None }
655 }
656
657 fn startup_mode(&self) -> ConsumerStartupMode {
658 ConsumerStartupMode::Explicit
659 }
660
661 fn set_security_context(&mut self, ctx: SecurityContext) {
662 self.security_ctx = Some(ctx);
663 }
664}
665
666async fn process_unary_request(
669 body: Vec<u8>,
670 metadata: tonic::metadata::MetadataMap,
671 req_desc: MessageDescriptor,
672 resp_desc: MessageDescriptor,
673 sender: mpsc::Sender<ExchangeEnvelope>,
674 kernel_auth: Option<KernelRequestAuth>,
675) -> Result<Vec<u8>, Status> {
676 let req_dyn = DynamicMessage::decode(req_desc, body.as_slice())
677 .map_err(|e| Status::invalid_argument(format!("failed to decode protobuf: {e}")))?;
678
679 let json = serde_json::to_value(&req_dyn).map_err(|e| {
680 Status::invalid_argument(format!("failed to convert protobuf to JSON: {e}"))
681 })?;
682
683 let mut msg = Message::new(Body::Json(json));
684 for (k, v) in extract_metadata(&metadata) {
685 msg.set_header(k, v);
686 }
687
688 let mut exchange = Exchange::new(msg);
689 if let Some(auth) = kernel_auth.as_ref() {
690 auth.apply_to(&mut exchange)?;
692 }
693
694 let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
695 let envelope = ExchangeEnvelope {
696 exchange,
697 reply_tx: Some(reply_tx),
698 };
699
700 sender
701 .send(envelope)
702 .await
703 .map_err(|_| Status::internal("pipeline channel closed"))?;
704
705 let result = reply_rx
706 .await
707 .map_err(|_| Status::internal("pipeline reply dropped"))?
708 .map_err(pipeline_error_to_status)?;
709
710 let resp_json = match result.input.body {
711 Body::Json(v) => v,
712 other => {
713 return Err(Status::internal(format!(
714 "expected JSON response body from pipeline, got {other:?}"
715 )));
716 }
717 };
718
719 let json_str = serde_json::to_string(&resp_json)
720 .map_err(|e| Status::internal(format!("failed to serialize response JSON: {e}")))?;
721 let mut de = serde_json::Deserializer::from_str(&json_str);
722 let resp_dyn = DynamicMessage::deserialize(resp_desc, &mut de)
723 .map_err(|e| Status::internal(format!("failed to parse JSON into protobuf: {e}")))?;
724
725 let mut buf = BytesMut::new();
726 resp_dyn
727 .encode(&mut buf)
728 .map_err(|e| Status::internal(format!("failed to encode protobuf response: {e}")))?;
729
730 Ok(buf.to_vec())
731}
732
733async fn process_server_streaming_request(
736 body: Vec<u8>,
737 metadata: tonic::metadata::MetadataMap,
738 req_desc: MessageDescriptor,
739 resp_desc: MessageDescriptor,
740 sender: mpsc::Sender<ExchangeEnvelope>,
741 reply_tx: mpsc::Sender<GrpcStreamItem>,
742 kernel_auth: Option<KernelRequestAuth>,
743) {
744 let req_dyn = match DynamicMessage::decode(req_desc, body.as_slice()) {
745 Ok(m) => m,
746 Err(e) => {
747 let _ = reply_tx
748 .send(GrpcStreamItem::Error(Status::invalid_argument(format!(
749 "failed to decode protobuf: {e}"
750 ))))
751 .await;
752 return;
753 }
754 };
755
756 let json = match serde_json::to_value(&req_dyn) {
757 Ok(v) => v,
758 Err(e) => {
759 let _ = reply_tx
760 .send(GrpcStreamItem::Error(Status::invalid_argument(format!(
761 "failed to convert protobuf to JSON: {e}"
762 ))))
763 .await;
764 return;
765 }
766 };
767
768 let mut msg = Message::new(Body::Json(json));
769 for (k, v) in extract_metadata(&metadata) {
770 msg.set_header(k, v);
771 }
772
773 let observer = GrpcStreamObserver::new(reply_tx.clone(), resp_desc);
774 let observer_id = next_observer_id();
775 register_observer(observer_id.clone(), observer.clone());
776 let _guard = ObserverGuard::new(observer_id.clone());
777
778 let mut exchange = Exchange::new(msg);
779 if let Some(auth) = kernel_auth.as_ref()
780 && let Err(status) = auth.apply_to(&mut exchange)
781 {
782 let _ = reply_tx.send(GrpcStreamItem::Error(status)).await;
783 return;
784 }
785 exchange.set_property("CamelGrpcStreamObserverId", Value::String(observer_id));
786
787 let (pipeline_reply_tx, pipeline_reply_rx) = tokio::sync::oneshot::channel();
792 let envelope = ExchangeEnvelope {
793 exchange,
794 reply_tx: Some(pipeline_reply_tx),
795 };
796
797 if sender.send(envelope).await.is_err() {
798 let _ = reply_tx
799 .send(GrpcStreamItem::Error(Status::internal(
800 "pipeline channel closed",
801 )))
802 .await;
803 return;
804 }
805
806 if let Ok(Err(e)) = pipeline_reply_rx.await {
813 observer.on_error(pipeline_error_to_status(e)).await;
814 }
815
816 reply_tx.closed().await;
821}
822
823async fn process_client_streaming_request(
826 mut body_rx: mpsc::Receiver<Vec<u8>>,
827 metadata: tonic::metadata::MetadataMap,
828 req_desc: MessageDescriptor,
829 resp_desc: MessageDescriptor,
830 sender: mpsc::Sender<ExchangeEnvelope>,
831 reply_tx: tokio::sync::oneshot::Sender<GrpcReply>,
832 kernel_auth: Option<KernelRequestAuth>,
833) {
834 while let Some(body) = body_rx.recv().await {
835 let req_dyn = match DynamicMessage::decode(req_desc.clone(), body.as_slice()) {
836 Ok(d) => d,
837 Err(e) => {
838 let _ = reply_tx.send(GrpcReply::Err(Status::invalid_argument(format!(
839 "failed to decode protobuf: {e}"
840 ))));
841 return;
842 }
843 };
844
845 let json = match serde_json::to_value(&req_dyn) {
846 Ok(j) => j,
847 Err(e) => {
848 let _ = reply_tx.send(GrpcReply::Err(Status::internal(format!(
849 "failed to convert protobuf to JSON: {e}"
850 ))));
851 return;
852 }
853 };
854
855 let mut msg = Message::new(Body::Json(json));
856 for (k, v) in extract_metadata(&metadata) {
857 msg.set_header(k, v);
858 }
859 msg.set_header(
860 "CamelGrpcClientStreaming".to_string(),
861 serde_json::Value::Bool(true),
862 );
863
864 let mut exchange = Exchange::new(msg);
865 if let Some(auth) = kernel_auth.as_ref()
866 && let Err(status) = auth.apply_to(&mut exchange)
867 {
868 let _ = reply_tx.send(GrpcReply::Err(status));
869 return;
870 }
871 let (reply_tx_pipe, reply_rx_pipe) = tokio::sync::oneshot::channel();
872 let envelope = ExchangeEnvelope {
873 exchange,
874 reply_tx: Some(reply_tx_pipe),
875 };
876
877 if sender.send(envelope).await.is_err() {
878 let _ = reply_tx.send(GrpcReply::Err(Status::internal("pipeline channel closed")));
879 return;
880 }
881
882 let _ = reply_rx_pipe.await;
884 }
885
886 let mut completion_msg = Message::new(Body::Json(serde_json::Value::Null));
888 for (k, v) in extract_metadata(&metadata) {
889 completion_msg.set_header(k, v);
890 }
891 completion_msg.set_header(
892 "CamelGrpcClientStreaming".to_string(),
893 serde_json::Value::Bool(true),
894 );
895 completion_msg.set_header(
896 "CamelGrpcClientStreamComplete".to_string(),
897 serde_json::Value::Bool(true),
898 );
899
900 let mut completion_exchange = Exchange::new(completion_msg);
901 if let Some(auth) = kernel_auth.as_ref()
902 && let Err(status) = auth.apply_to(&mut completion_exchange)
903 {
904 let _ = reply_tx.send(GrpcReply::Err(status));
905 return;
906 }
907 let (reply_tx_pipe, reply_rx_pipe) = tokio::sync::oneshot::channel();
908 let envelope = ExchangeEnvelope {
909 exchange: completion_exchange,
910 reply_tx: Some(reply_tx_pipe),
911 };
912
913 if sender.send(envelope).await.is_err() {
914 let _ = reply_tx.send(GrpcReply::Err(Status::internal("pipeline channel closed")));
915 return;
916 }
917
918 let result = match reply_rx_pipe.await {
920 Ok(Ok(exchange)) => exchange,
921 Ok(Err(e)) => {
922 let _ = reply_tx.send(GrpcReply::Err(pipeline_error_to_status(e)));
923 return;
924 }
925 Err(_) => {
926 let _ = reply_tx.send(GrpcReply::Err(Status::internal("pipeline reply dropped")));
927 return;
928 }
929 };
930
931 let resp_json = match result.input.body {
932 Body::Json(v) => v,
933 other => {
934 let _ = reply_tx.send(GrpcReply::Err(Status::internal(format!(
935 "expected JSON response body from pipeline, got {other:?}"
936 ))));
937 return;
938 }
939 };
940
941 let encoded = match json_to_protobuf_bytes(resp_json, resp_desc) {
942 Ok(b) => b,
943 Err(e) => {
944 let _ = reply_tx.send(GrpcReply::Err(Status::internal(format!(
945 "failed to encode response: {e}",
946 ))));
947 return;
948 }
949 };
950
951 let _ = reply_tx.send(GrpcReply::Ok(encoded));
952}
953
954async fn process_bidi_request(
957 mut body_rx: mpsc::Receiver<Vec<u8>>,
958 metadata: tonic::metadata::MetadataMap,
959 req_desc: MessageDescriptor,
960 resp_desc: MessageDescriptor,
961 sender: mpsc::Sender<ExchangeEnvelope>,
962 reply_tx: mpsc::Sender<GrpcStreamItem>,
963 kernel_auth: Option<KernelRequestAuth>,
964) {
965 let observer = GrpcStreamObserver::new(reply_tx.clone(), resp_desc);
966 let observer_id = next_observer_id();
967 register_observer(observer_id.clone(), observer.clone());
968 let _guard = ObserverGuard::new(observer_id.clone());
969
970 let sender_clone = sender.clone();
972 let metadata_clone = metadata.clone();
973 let req_desc_clone = req_desc.clone();
974
975 let forward_task = tokio::spawn(async move {
976 let mut sequence: u64 = 0;
977 while let Some(body) = body_rx.recv().await {
978 let req_dyn = match DynamicMessage::decode(req_desc_clone.clone(), body.as_slice()) {
979 Ok(m) => m,
980 Err(e) => {
981 let _ = observer
982 .on_error(Status::invalid_argument(format!(
983 "failed to decode protobuf: {e}"
984 )))
985 .await;
986 continue;
987 }
988 };
989
990 let json = match serde_json::to_value(&req_dyn) {
991 Ok(v) => v,
992 Err(e) => {
993 let _ = observer
994 .on_error(Status::invalid_argument(format!(
995 "failed to convert protobuf to JSON: {e}"
996 )))
997 .await;
998 continue;
999 }
1000 };
1001
1002 let mut msg = Message::new(Body::Json(json));
1003 for (k, v) in extract_metadata(&metadata_clone) {
1004 msg.set_header(k, v);
1005 }
1006
1007 msg.set_header(
1008 "CamelGrpcBidiSequence",
1009 serde_json::Value::Number(sequence.into()),
1010 );
1011 sequence += 1;
1012
1013 let mut exchange = Exchange::new(msg);
1014 if let Some(auth) = kernel_auth.as_ref()
1015 && let Err(status) = auth.apply_to(&mut exchange)
1016 {
1017 let _ = observer.on_error(status).await;
1018 break;
1019 }
1020 exchange.set_property(
1021 "CamelGrpcStreamObserverId",
1022 Value::String(observer_id.clone()),
1023 );
1024
1025 let (pipeline_reply_tx, pipeline_reply_rx) = tokio::sync::oneshot::channel();
1032 let envelope = ExchangeEnvelope {
1033 exchange,
1034 reply_tx: Some(pipeline_reply_tx),
1035 };
1036
1037 if sender_clone.send(envelope).await.is_err() {
1038 let _ = observer
1039 .on_error(Status::internal("pipeline channel closed"))
1040 .await;
1041 break;
1042 }
1043
1044 let verdict_observer = observer.clone();
1045 tokio::spawn(async move {
1046 if let Ok(Err(e)) = pipeline_reply_rx.await {
1047 verdict_observer.on_error(pipeline_error_to_status(e)).await;
1048 }
1049 });
1050 }
1051
1052 observer.on_completed().await;
1054 });
1055
1056 let _ = forward_task.await;
1058}
1059
1060#[cfg(test)]
1061mod tests {
1062 use super::*;
1063
1064 #[test]
1065 fn grpc_credential_sources_uncarryable_rejected_at_load() {
1066 let query = CredentialSource::QueryParam {
1067 param: "ticket".to_string(),
1068 };
1069 let err = validate_credential_sources(&[query]).unwrap_err();
1070 let msg = err.to_string();
1071 assert!(msg.contains("query_param"), "message was: {msg}");
1072 assert!(msg.contains("grpc"), "message was: {msg}");
1073
1074 let cookie = CredentialSource::Cookie {
1075 name: "session".to_string(),
1076 };
1077 let err = validate_credential_sources(&[cookie]).unwrap_err();
1078 let msg = err.to_string();
1079 assert!(msg.contains("cookie"), "message was: {msg}");
1080 assert!(msg.contains("grpc"), "message was: {msg}");
1081
1082 let carryable = vec![
1084 CredentialSource::AuthorizationHeader,
1085 CredentialSource::Header {
1086 name: "x-api-key".to_string(),
1087 },
1088 ];
1089 assert!(validate_credential_sources(&carryable).is_ok());
1090 assert!(validate_credential_sources(&[]).is_ok());
1091 }
1092}