From 5f85e9c9b1c298006217b835e3037cbc56acbc84 Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Sat, 10 Jan 2026 17:40:10 +0100 Subject: [PATCH 1/7] feat: add gRPC-Web module --- Cargo.lock | 22 ++ docs/configuration-kdl.md | 4 +- docs/use-cases/reverse-proxy.md | 13 + ferron-build.yaml | 3 + ferron-load-modules/Cargo.toml | 1 + ferron-modules-builtin/Cargo.toml | 9 + .../src/optional/grpcweb.rs | 265 ++++++++++++++++++ ferron-modules-builtin/src/optional/mod.rs | 4 + 8 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 ferron-modules-builtin/src/optional/grpcweb.rs diff --git a/Cargo.lock b/Cargo.lock index d6fddeec..d02981cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1470,6 +1470,10 @@ dependencies = [ "tokio-rustls", "tokio-test", "tokio-util", + "tonic", + "tonic-web", + "tower-layer", + "tower-service", "urlencoding", "webpki-roots 1.0.5", "xxhash-rust", @@ -4459,6 +4463,24 @@ dependencies = [ "tonic", ] +[[package]] +name = "tonic-web" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75214f6b6bd28c19aa752ac09fdf0eea546095670906c21fe3940e180a4c43f2" +dependencies = [ + "base64", + "bytes", + "http 1.4.0", + "http-body 1.0.1", + "pin-project", + "tokio-stream", + "tonic", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.5.2" diff --git a/docs/configuration-kdl.md b/docs/configuration-kdl.md index e4f6f35c..eaf61f8d 100644 --- a/docs/configuration-kdl.md +++ b/docs/configuration-kdl.md @@ -522,7 +522,9 @@ example.com { - This directive specifies whether the reverse proxy uses HTTP/2 protocol (without HTTP/1.1 fallback) when connecting to backend servers. When the backend server is connected via HTTPS, the reverse proxy negotiates HTTP/2 during the TLS handshake. When the backend server is connected via HTTP, the reverse proxy uses HTTP/2 with prior knowledge. This directive can be used when proxying gRPC requests. Default: `proxy_http2_only #false` - `proxy_proxy_header ` (_rproxy_ module; Ferron 2.1.0 or newer) - This directive specifies the version of the PROXY protocol header to be sent to backend servers when acting as a reverse proxy. Supported versions are `"v1"` (PROXY protocol version 1) and `"v2"` (PROXY protocol version 2). If specified with `#null` value, no PROXY protocol header is sent. Default: `proxy_proxy_header #null` - +- `grpcweb [enable_grpcweb: bool]` (_grpcweb_ module; Ferron UNRELEASED or newer) + - This directive specifies whether to translate gRPC-Web requests into gRPC ones. Default: `grpcweb #false` + **Configuration example:** ```kdl diff --git a/docs/use-cases/reverse-proxy.md b/docs/use-cases/reverse-proxy.md index a0947f30..2c9441a9 100644 --- a/docs/use-cases/reverse-proxy.md +++ b/docs/use-cases/reverse-proxy.md @@ -146,6 +146,19 @@ grpc.example.com { } ``` +## gRPC-Web translation + +Ferron supports translation of gRPC-Web requests into gRPC ones. This can be useful when you want to expose a gRPC service over HTTP/2, but your clients only support gRPC-Web. To configure Ferron for gRPC-Web translation, you can use this configuration: + +```kdl +// Example configuration with gRPC-Web translation. Replace "grpcweb.example.com" with your domain name. +grpcweb.example.com { + grpcweb // Translate gRPC-Web requests into gRPC ones + proxy "http://localhost:3000/" // Replace "http://localhost:3000" with the backend server URL + proxy_http2_only // Enables HTTP/2-only proxying to support gRPC proxying +} +``` + ## Example: Ferron multiplexing to several backend servers In this example, the `example.com` and `bar.example.com` domains point to a server running Ferron. diff --git a/ferron-build.yaml b/ferron-build.yaml index 774bf5ca..8d42f8e6 100644 --- a/ferron-build.yaml +++ b/ferron-build.yaml @@ -32,6 +32,9 @@ modules: - builtin: true cargo_feature: replace loader: ReplaceModuleLoader + - builtin: true + cargo_feature: grpcweb + loader: GrpcWebModuleLoader - builtin: true cargo_feature: rproxy loader: ReverseProxyModuleLoader diff --git a/ferron-load-modules/Cargo.toml b/ferron-load-modules/Cargo.toml index 9a9aff0a..710d0473 100644 --- a/ferron-load-modules/Cargo.toml +++ b/ferron-load-modules/Cargo.toml @@ -12,6 +12,7 @@ ferron-modules-builtin = { workspace = true, features = [ "fauth", "fcgi", "fproxy", + "grpcweb", "limit", "replace", "rproxy", diff --git a/ferron-modules-builtin/Cargo.toml b/ferron-modules-builtin/Cargo.toml index ca512a3f..cc590468 100644 --- a/ferron-modules-builtin/Cargo.toml +++ b/ferron-modules-builtin/Cargo.toml @@ -95,6 +95,12 @@ smallvec = { version = "1.15.0", features = [ "const_generics", ] } +# gRPC +tonic-web = { version = "0.14.2", optional = true } +tonic = { version = "0.14.2", optional = true, default-features = false } +tower-layer = { version = "0.3.3", optional = true } +tower-service = { version = "0.3.3", optional = true } + [dev-dependencies] tokio-test = "0.4.4" shiba = { workspace = true } @@ -107,6 +113,7 @@ default = [ "fauth", "fcgi", "fproxy", + "grpcweb", "limit", "replace", "rproxy", @@ -121,6 +128,7 @@ default-tokio = [ "fauth", "fcgi", "fproxy", + "grpcweb", "limit", "replace", "rproxy", @@ -134,6 +142,7 @@ dcompress = ["async-compression"] fauth = ["rand", "kanal", "arc-swap"] fcgi = ["tokio-util/codec", "httparse", "memchr"] fproxy = [] +grpcweb = ["tonic-web", "tower-layer", "tower-service", "tonic"] limit = ["tokenbucket"] replace = ["memchr"] rproxy = ["rand", "ppp", "connpool"] diff --git a/ferron-modules-builtin/src/optional/grpcweb.rs b/ferron-modules-builtin/src/optional/grpcweb.rs new file mode 100644 index 00000000..e31c62d6 --- /dev/null +++ b/ferron-modules-builtin/src/optional/grpcweb.rs @@ -0,0 +1,265 @@ +use std::collections::HashSet; +use std::error::Error; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll, Waker}; + +use async_trait::async_trait; +use bytes::Bytes; +use ferron_common::get_entries_for_validation; +use futures_util::FutureExt; +use http_body_util::combinators::BoxBody; +use http_body_util::BodyExt; +use hyper::{Request, Response}; +use tonic_web::{GrpcWebLayer, ResponseFuture}; +use tower_layer::Layer; +use tower_service::Service; + +use ferron_common::logging::ErrorLogger; +use ferron_common::modules::{Module, ModuleHandlers, ModuleLoader, ResponseData, SocketData}; +use ferron_common::{config::ServerConfiguration, util::ModuleCache}; + +/// A Tower service that errors out with an original request +struct ReturnRequestService { + tx: Option>>, + rx: Option>>>, +} + +impl Service> for ReturnRequestService { + type Response = Response>; + type Error = anyhow::Error; + type Future = Pin> + Send + Sync>>; + + fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + fn call(&mut self, request: Request) -> Self::Future { + if let Some(tx) = self.tx.take() { + let _ = tx.send(request); + } + let rx = self.rx.take(); + Box::pin(async move { + if let Some(rx) = rx { + rx.await.map_err(|_| anyhow::anyhow!("Response body missing")) + } else { + Err(anyhow::anyhow!("Response body missing")) + } + }) + } +} + +/// Unsafely sync Tonic body +struct SyncTonicBody { + body: Pin>, +} + +impl SyncTonicBody { + /// Create a new `SyncTonicBody` from a `tonic::body::Body`. + /// + /// ## Safety + /// This function is unsafe because it does not check if the inner body is `Sync`. + unsafe fn new(body: tonic::body::Body) -> Self { + Self { body: Box::pin(body) } + } +} + +impl hyper::body::Body for SyncTonicBody { + type Data = ::Data; + type Error = ::Error; + + fn poll_frame( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll, Self::Error>>> { + Pin::new(&mut self.body).poll_frame(cx) + } + + fn is_end_stream(&self) -> bool { + self.body.is_end_stream() + } + + fn size_hint(&self) -> hyper::body::SizeHint { + // Use default size hint, to avoid conflicts with gRPC + hyper::body::SizeHint::default() + } +} + +// Safety: There's a safety warning in the `SyncTonicBody` struct's constructor. +unsafe impl Sync for SyncTonicBody {} + +/// A gRPC-Web module loader +pub struct GrpcWebModuleLoader { + cache: ModuleCache, +} + +impl Default for GrpcWebModuleLoader { + fn default() -> Self { + Self::new() + } +} + +impl GrpcWebModuleLoader { + /// Creates a new module loader + pub fn new() -> Self { + Self { + cache: ModuleCache::new(vec![]), + } + } +} + +impl ModuleLoader for GrpcWebModuleLoader { + fn load_module( + &mut self, + config: &ServerConfiguration, + _global_config: Option<&ServerConfiguration>, + _secondary_runtime: &tokio::runtime::Runtime, + ) -> Result, Box> { + Ok( + self + .cache + .get_or_init::<_, Box>(config, move |_| { + Ok(Arc::new(GrpcWebModule { + layer: GrpcWebLayer::new(), + })) + })?, + ) + } + + fn get_requirements(&self) -> Vec<&'static str> { + vec!["grpcweb"] + } + + fn validate_configuration( + &self, + config: &ServerConfiguration, + used_properties: &mut HashSet, + ) -> Result<(), Box> { + if let Some(entries) = get_entries_for_validation!("grpcweb", config, used_properties) { + for entry in &entries.inner { + if entry.values.len() != 1 { + return Err(anyhow::anyhow!("The `grpcweb` configuration property must have exactly one value").into()); + } else if !entry.values[0].is_bool() { + return Err(anyhow::anyhow!("Invalid gRPC-Web translation enabling option").into()); + } + } + } + + Ok(()) + } +} + +/// A gRPC-Web module +struct GrpcWebModule { + layer: GrpcWebLayer, +} + +impl Module for GrpcWebModule { + fn get_module_handlers(&self) -> Box { + Box::new(GrpcWebModuleHandlers { + layer: self.layer.clone(), + service: None, + }) + } +} + +/// Handlers for the gRPC-Web module +#[allow(clippy::type_complexity)] +struct GrpcWebModuleHandlers { + layer: GrpcWebLayer, + service: Option<( + tokio::sync::oneshot::Sender>>, + ResponseFuture< + Pin>, anyhow::Error>> + Send + Sync>>, + >, + )>, +} + +#[async_trait(?Send)] +impl ModuleHandlers for GrpcWebModuleHandlers { + async fn request_handler( + &mut self, + request: Request>, + _config: &ServerConfiguration, + _socket_data: &SocketData, + _error_logger: &ErrorLogger, + ) -> Result> { + let (tx, rx) = tokio::sync::oneshot::channel(); + let (request_tx, request_rx) = tokio::sync::oneshot::channel(); + let mut service = self.layer.layer(ReturnRequestService { + tx: Some(request_tx), + rx: Some(rx), + }); + futures_util::future::poll_fn(|cx| { + tower_service::Service::>>::poll_ready(&mut service, cx) + }) + .await?; + let mut call_future = service.call(request); + match call_future.poll_unpin(&mut Context::from_waker(Waker::noop())) { + Poll::Ready(response) => { + let (response_parts, _) = response?.into_parts(); + Ok(ResponseData { + request: None, + response: None, + response_status: Some(response_parts.status), + response_headers: Some(response_parts.headers), + new_remote_address: None, + }) + } + Poll::Pending => { + let request = request_rx.await?; + let mut request = request.map(|b| { + // Safety: the tonic::body::Body (which SyncTonicBody wraps) is wrapped around a BoxedBody, + // which is Send + Sync. + let wrapped_body = unsafe { SyncTonicBody::new(b) }; + wrapped_body + .map_err(|e| std::io::Error::other(format!("gRPC error: {e}"))) + .boxed() + }); + self.service = Some((tx, call_future)); + + // Remove the Content-Length header to avoid conflicts with gRPC + while request.headers_mut().remove(hyper::header::CONTENT_LENGTH).is_some() {} + + Ok(ResponseData { + request: Some(request), + response: None, + response_status: None, + response_headers: None, + new_remote_address: None, + }) + } + } + } + + async fn response_modifying_handler( + &mut self, + response: Response>, + ) -> Result>, Box> { + if response + .headers() + .get(hyper::header::CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .is_none_or(|value| !(value == "application/grpc" || value.starts_with("application/grpc+"))) + { + // If the response is not gRPC, don't turn it into gRPC-Web one + return Ok(response); + } + + if let Some((tx, call_future)) = self.service.take() { + tx.send(response).unwrap_or_default(); + let response = call_future.await?; + Ok(response.map(|b| { + // Safety: the tonic::body::Body (which SyncTonicBody wraps) is wrapped around a BoxedBody, + // which is Send + Sync. + let wrapped_body = unsafe { SyncTonicBody::new(b) }; + wrapped_body + .map_err(|e| std::io::Error::other(format!("gRPC error: {e}"))) + .boxed() + })) + } else { + Ok(response) + } + } +} diff --git a/ferron-modules-builtin/src/optional/mod.rs b/ferron-modules-builtin/src/optional/mod.rs index 80a95efc..33f65dc4 100644 --- a/ferron-modules-builtin/src/optional/mod.rs +++ b/ferron-modules-builtin/src/optional/mod.rs @@ -10,6 +10,8 @@ mod fauth; mod fcgi; #[cfg(feature = "fproxy")] mod fproxy; +#[cfg(feature = "grpcweb")] +mod grpcweb; #[cfg(feature = "limit")] mod limit; #[cfg(feature = "replace")] @@ -33,6 +35,8 @@ pub use fauth::*; pub use fcgi::*; #[cfg(feature = "fproxy")] pub use fproxy::*; +#[cfg(feature = "grpcweb")] +pub use grpcweb::*; #[cfg(feature = "limit")] pub use limit::*; #[cfg(feature = "static")] From 1e6ed0b73120b48fc3137dbfef7126cb449dfb54 Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Sat, 10 Jan 2026 17:57:13 +0100 Subject: [PATCH 2/7] refactor: rename `ReturnRequestService` to match the use case --- ferron-modules-builtin/src/optional/grpcweb.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ferron-modules-builtin/src/optional/grpcweb.rs b/ferron-modules-builtin/src/optional/grpcweb.rs index e31c62d6..ba2942f9 100644 --- a/ferron-modules-builtin/src/optional/grpcweb.rs +++ b/ferron-modules-builtin/src/optional/grpcweb.rs @@ -20,13 +20,13 @@ use ferron_common::logging::ErrorLogger; use ferron_common::modules::{Module, ModuleHandlers, ModuleLoader, ResponseData, SocketData}; use ferron_common::{config::ServerConfiguration, util::ModuleCache}; -/// A Tower service that errors out with an original request -struct ReturnRequestService { +/// A Tower service that sends the request and waits for a response +struct InnerWaitService { tx: Option>>, rx: Option>>>, } -impl Service> for ReturnRequestService { +impl Service> for InnerWaitService { type Response = Response>; type Error = anyhow::Error; type Future = Pin> + Send + Sync>>; @@ -187,7 +187,7 @@ impl ModuleHandlers for GrpcWebModuleHandlers { ) -> Result> { let (tx, rx) = tokio::sync::oneshot::channel(); let (request_tx, request_rx) = tokio::sync::oneshot::channel(); - let mut service = self.layer.layer(ReturnRequestService { + let mut service = self.layer.layer(InnerWaitService { tx: Some(request_tx), rx: Some(rx), }); From 24525a743a93a0399cb897768ff5b73c661f2258 Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Sat, 10 Jan 2026 18:37:19 +0100 Subject: [PATCH 3/7] docs: add mention of `grpcweb` module in server modules page --- docs/modules.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/modules.md b/docs/modules.md index 9778cc54..22236d32 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -12,6 +12,7 @@ The following modules are built into Ferron and are enabled by default: - _fauth_ - this module enables authentication forwarded to the authentication server. - _fcgi_ - this module enables the support for connecting to FastCGI servers. - _fproxy_ - this module enables forward proxy functionality. +- _grpcweb_ (Ferron UNRELEASED and newer) - this module enables support for translation of gRPC-Web data into gRPC. - _limit_ (Ferron 2.0.0 and newer) - this module enables rate limits. - _replace_ (Ferron 2.0.0 and newer) - this module enables replacement of strings in response bodies. - _rproxy_ - this module enables reverse proxy functionality. From 239f375aef52168c33909126c8b76a0eca45bb99 Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Sat, 10 Jan 2026 19:41:32 +0100 Subject: [PATCH 4/7] chore: suggest inlining some functions in gRPC-Web module --- ferron-modules-builtin/src/optional/grpcweb.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ferron-modules-builtin/src/optional/grpcweb.rs b/ferron-modules-builtin/src/optional/grpcweb.rs index ba2942f9..90ffc297 100644 --- a/ferron-modules-builtin/src/optional/grpcweb.rs +++ b/ferron-modules-builtin/src/optional/grpcweb.rs @@ -31,10 +31,12 @@ impl Service> for InnerWaitService { type Error = anyhow::Error; type Future = Pin> + Send + Sync>>; + #[inline] fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll> { std::task::Poll::Ready(Ok(())) } + #[inline] fn call(&mut self, request: Request) -> Self::Future { if let Some(tx) = self.tx.take() { let _ = tx.send(request); @@ -60,6 +62,7 @@ impl SyncTonicBody { /// /// ## Safety /// This function is unsafe because it does not check if the inner body is `Sync`. + #[inline] unsafe fn new(body: tonic::body::Body) -> Self { Self { body: Box::pin(body) } } @@ -69,6 +72,7 @@ impl hyper::body::Body for SyncTonicBody { type Data = ::Data; type Error = ::Error; + #[inline] fn poll_frame( mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, @@ -76,10 +80,12 @@ impl hyper::body::Body for SyncTonicBody { Pin::new(&mut self.body).poll_frame(cx) } + #[inline] fn is_end_stream(&self) -> bool { self.body.is_end_stream() } + #[inline] fn size_hint(&self) -> hyper::body::SizeHint { // Use default size hint, to avoid conflicts with gRPC hyper::body::SizeHint::default() From b846caba49ad771adfc9cccf1182d696380291d2 Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Sun, 11 Jan 2026 11:34:36 +0100 Subject: [PATCH 5/7] refactor: use `select!` macro instead of relying on service returning future resolving immediately --- ferron-modules-builtin/src/optional/grpcweb.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ferron-modules-builtin/src/optional/grpcweb.rs b/ferron-modules-builtin/src/optional/grpcweb.rs index 90ffc297..a3280537 100644 --- a/ferron-modules-builtin/src/optional/grpcweb.rs +++ b/ferron-modules-builtin/src/optional/grpcweb.rs @@ -3,12 +3,10 @@ use std::error::Error; use std::future::Future; use std::pin::Pin; use std::sync::Arc; -use std::task::{Context, Poll, Waker}; use async_trait::async_trait; use bytes::Bytes; use ferron_common::get_entries_for_validation; -use futures_util::FutureExt; use http_body_util::combinators::BoxBody; use http_body_util::BodyExt; use hyper::{Request, Response}; @@ -202,8 +200,10 @@ impl ModuleHandlers for GrpcWebModuleHandlers { }) .await?; let mut call_future = service.call(request); - match call_future.poll_unpin(&mut Context::from_waker(Waker::noop())) { - Poll::Ready(response) => { + ferron_common::runtime::select! { + biased; + + response = &mut call_future => { let (response_parts, _) = response?.into_parts(); Ok(ResponseData { request: None, @@ -213,8 +213,9 @@ impl ModuleHandlers for GrpcWebModuleHandlers { new_remote_address: None, }) } - Poll::Pending => { - let request = request_rx.await?; + request = request_rx => { + let request = request + .map_err(|_| anyhow::anyhow!("Failed to obtain the translated gRPC request"))?; let mut request = request.map(|b| { // Safety: the tonic::body::Body (which SyncTonicBody wraps) is wrapped around a BoxedBody, // which is Send + Sync. @@ -235,7 +236,7 @@ impl ModuleHandlers for GrpcWebModuleHandlers { response_headers: None, new_remote_address: None, }) - } + }, } } From 06e3424941106962a74dd799846e6458d9f113e3 Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Thu, 22 Jan 2026 15:49:31 +0100 Subject: [PATCH 6/7] test: add basic gRPC-Web tests --- .../tests/grpcweb/backend/.dockerignore | 2 + dockertest/tests/grpcweb/backend/.gitignore | 2 + dockertest/tests/grpcweb/backend/Dockerfile | 17 + dockertest/tests/grpcweb/backend/index.js | 60 + .../tests/grpcweb/backend/package-lock.json | 346 +++++ dockertest/tests/grpcweb/backend/package.json | 6 + dockertest/tests/grpcweb/docker-compose.yml | 27 + dockertest/tests/grpcweb/ferron.kdl | 5 + .../grpcweb/grpcweb-test-proxy/.dockerignore | 2 + .../grpcweb/grpcweb-test-proxy/.gitignore | 2 + .../grpcweb/grpcweb-test-proxy/Cargo.lock | 1194 +++++++++++++++++ .../grpcweb/grpcweb-test-proxy/Cargo.toml | 22 + .../grpcweb/grpcweb-test-proxy/Dockerfile | 29 + .../tests/grpcweb/grpcweb-test-proxy/build.rs | 19 + .../grpcweb/grpcweb-test-proxy/src/main.rs | 84 ++ dockertest/tests/grpcweb/hello.proto | 39 + dockertest/tests/grpcweb/run.sh | 13 + dockertest/tests/grpcweb/test.sh | 47 + 18 files changed, 1916 insertions(+) create mode 100644 dockertest/tests/grpcweb/backend/.dockerignore create mode 100644 dockertest/tests/grpcweb/backend/.gitignore create mode 100644 dockertest/tests/grpcweb/backend/Dockerfile create mode 100644 dockertest/tests/grpcweb/backend/index.js create mode 100644 dockertest/tests/grpcweb/backend/package-lock.json create mode 100644 dockertest/tests/grpcweb/backend/package.json create mode 100644 dockertest/tests/grpcweb/docker-compose.yml create mode 100644 dockertest/tests/grpcweb/ferron.kdl create mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/.dockerignore create mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/.gitignore create mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.lock create mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.toml create mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/Dockerfile create mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/build.rs create mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/src/main.rs create mode 100644 dockertest/tests/grpcweb/hello.proto create mode 100755 dockertest/tests/grpcweb/run.sh create mode 100755 dockertest/tests/grpcweb/test.sh diff --git a/dockertest/tests/grpcweb/backend/.dockerignore b/dockertest/tests/grpcweb/backend/.dockerignore new file mode 100644 index 00000000..80db1637 --- /dev/null +++ b/dockertest/tests/grpcweb/backend/.dockerignore @@ -0,0 +1,2 @@ +/node_modules/ +/Dockerfile diff --git a/dockertest/tests/grpcweb/backend/.gitignore b/dockertest/tests/grpcweb/backend/.gitignore new file mode 100644 index 00000000..793e78bb --- /dev/null +++ b/dockertest/tests/grpcweb/backend/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +/hello.proto diff --git a/dockertest/tests/grpcweb/backend/Dockerfile b/dockertest/tests/grpcweb/backend/Dockerfile new file mode 100644 index 00000000..076bc0c1 --- /dev/null +++ b/dockertest/tests/grpcweb/backend/Dockerfile @@ -0,0 +1,17 @@ +# Use the official Node.js image +FROM node:22-alpine + +# Set the working directory +WORKDIR /app + +# Copy files into the container +COPY . . + +# Install dependencies +RUN npm install + +# Expose port 3000 +EXPOSE 3000 + +# Start the application +CMD ["node", "index.js"] diff --git a/dockertest/tests/grpcweb/backend/index.js b/dockertest/tests/grpcweb/backend/index.js new file mode 100644 index 00000000..74c6d5fd --- /dev/null +++ b/dockertest/tests/grpcweb/backend/index.js @@ -0,0 +1,60 @@ +// Based on https://github.com/grpc/grpc-node/blob/master/examples/helloworld/dynamic_codegen/greeter_server.js, +// with a custom .proto file path +/* + * + * Copyright 2015 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const PROTO_PATH = __dirname + "/hello.proto"; + +const grpc = require("@grpc/grpc-js"); +const protoLoader = require("@grpc/proto-loader"); +const packageDefinition = protoLoader.loadSync(PROTO_PATH, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, +}); +const hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld; + +/** + * Implements the SayHello RPC method. + */ +function sayHello(call, callback) { + callback(null, { message: "Hello " + call.request.name }); +} + +/** + * Starts an RPC server that receives requests for the Greeter service at the + * sample server port + */ +function main() { + var server = new grpc.Server(); + server.addService(hello_proto.Greeter.service, { sayHello: sayHello }); + server.bindAsync( + "0.0.0.0:50051", + grpc.ServerCredentials.createInsecure(), + (err, port) => { + if (err != null) { + return console.error(err); + } + console.log(`gRPC listening on ${port}`); + }, + ); +} + +main(); diff --git a/dockertest/tests/grpcweb/backend/package-lock.json b/dockertest/tests/grpcweb/backend/package-lock.json new file mode 100644 index 00000000..a22ed917 --- /dev/null +++ b/dockertest/tests/grpcweb/backend/package-lock.json @@ -0,0 +1,346 @@ +{ + "name": "backend", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@grpc/grpc-js": "^1.14.1", + "@grpc/proto-loader": "^0.8.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@types/node": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/dockertest/tests/grpcweb/backend/package.json b/dockertest/tests/grpcweb/backend/package.json new file mode 100644 index 00000000..d6a1acce --- /dev/null +++ b/dockertest/tests/grpcweb/backend/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@grpc/grpc-js": "^1.14.1", + "@grpc/proto-loader": "^0.8.0" + } +} diff --git a/dockertest/tests/grpcweb/docker-compose.yml b/dockertest/tests/grpcweb/docker-compose.yml new file mode 100644 index 00000000..200118ac --- /dev/null +++ b/dockertest/tests/grpcweb/docker-compose.yml @@ -0,0 +1,27 @@ +services: + # The web server to test + ferron: + build: + context: ../../.. + dockerfile: Dockerfile.test + volumes: + - ./ferron.kdl:/etc/ferron.kdl + + # The backend server for testing + backend: + build: + context: ./backend + + # The test proxy server for testing + grpcweb-test-proxy: + build: + context: ./grpcweb-test-proxy + environment: + GRPCWEB_TEST_PROXY_LISTEN_ADDR: "0.0.0.0:8080" + GRPCWEB_TEST_PROXY_BACKEND_URL: "http://ferron/" + + # A container to run tests + test-runner: + build: + context: ../../runner + command: "tail -f /dev/null" diff --git a/dockertest/tests/grpcweb/ferron.kdl b/dockertest/tests/grpcweb/ferron.kdl new file mode 100644 index 00000000..35bc3544 --- /dev/null +++ b/dockertest/tests/grpcweb/ferron.kdl @@ -0,0 +1,5 @@ +:80 { + grpcweb + proxy "http://backend:50051/" + proxy_http2_only +} diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/.dockerignore b/dockertest/tests/grpcweb/grpcweb-test-proxy/.dockerignore new file mode 100644 index 00000000..4fffb2f8 --- /dev/null +++ b/dockertest/tests/grpcweb/grpcweb-test-proxy/.dockerignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/.gitignore b/dockertest/tests/grpcweb/grpcweb-test-proxy/.gitignore new file mode 100644 index 00000000..5939d4e3 --- /dev/null +++ b/dockertest/tests/grpcweb/grpcweb-test-proxy/.gitignore @@ -0,0 +1,2 @@ +/target +/hello.proto diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.lock b/dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.lock new file mode 100644 index 00000000..5113f2f4 --- /dev/null +++ b/dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.lock @@ -0,0 +1,1194 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "axum-macros", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "grpcweb-test-proxy" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "hyper-util", + "prost", + "serde", + "tokio", + "tonic", + "tonic-prost", + "tonic-prost-build", + "tonic-web", + "tower", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "pulldown-cmark", + "pulldown-cmark-to-cmark", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + +[[package]] +name = "pulldown-cmark" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" +dependencies = [ + "pulldown-cmark", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "ryu" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" +dependencies = [ + "async-trait", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "socket2", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c40aaccc9f9eccf2cd82ebc111adc13030d23e887244bc9cfa5d1d636049de3" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tonic-prost" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tonic-prost-build" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a16cba4043dc3ff43fcb3f96b4c5c154c64cbd18ca8dce2ab2c6a451d058a2" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn", + "tempfile", + "tonic-build", +] + +[[package]] +name = "tonic-web" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75214f6b6bd28c19aa752ac09fdf0eea546095670906c21fe3940e180a4c43f2" +dependencies = [ + "base64", + "bytes", + "http", + "http-body", + "pin-project", + "tokio-stream", + "tonic", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "zmij" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.toml b/dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.toml new file mode 100644 index 00000000..73acf0ef --- /dev/null +++ b/dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "grpcweb-test-proxy" +version = "0.1.0" +edition = "2021" + +[dependencies] +hyper-util = { version = "0.1.19", features = ["client-legacy"] } +tokio = { version = "1.49.0", features = ["full"] } +tonic = { version = "0.14.2", default-features = false, features = ["transport", "codegen"] } +tonic-web = "0.14.2" +tower = "0.5.3" +prost = "0.14.3" +tonic-prost = "0.14.2" +axum = { version = "0.8.8", features = ["macros"] } +anyhow = "1.0.100" +serde = { version = "1.0.228", features = ["derive"] } + +[build-dependencies] +tonic-prost-build = "0.14.2" + +[package.metadata.cargo-shear] +ignored = ["tonic-prost", "prost"] diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/Dockerfile b/dockertest/tests/grpcweb/grpcweb-test-proxy/Dockerfile new file mode 100644 index 00000000..1d0ef2cd --- /dev/null +++ b/dockertest/tests/grpcweb/grpcweb-test-proxy/Dockerfile @@ -0,0 +1,29 @@ +# Use the official Rust image as a build stage +FROM rust:alpine AS builder + +# Set the working directory +WORKDIR /usr/src/grpcweb-test-proxy + +# Copy the source code +COPY . . + +# Install required packages +RUN apk add musl-dev protoc + +# Build the actual application (cache dependencies with BuildKit) +RUN --mount=type=cache,target=/usr/local/cargo/git \ + --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,sharing=private,target=/usr/src/grpcweb-test-proxy/target \ + # Debug build would work, since this is just a test proxy anyway... + cargo build && \ + # Copy executables out of the cache + mkdir .dist && cp target/debug/grpcweb-test-proxy .dist/grpcweb-test-proxy + +# Use alpine as the final stage +FROM alpine:latest + +# Copy the executable from the build stage +COPY --from=builder /usr/src/grpcweb-test-proxy/.dist/grpcweb-test-proxy /usr/local/bin/grpcweb-test-proxy + +# Set the entrypoint +ENTRYPOINT ["/usr/local/bin/grpcweb-test-proxy"] diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/build.rs b/dockertest/tests/grpcweb/grpcweb-test-proxy/build.rs new file mode 100644 index 00000000..d664e057 --- /dev/null +++ b/dockertest/tests/grpcweb/grpcweb-test-proxy/build.rs @@ -0,0 +1,19 @@ +use std::path::PathBuf; + +use tonic_prost_build::configure; + +fn main() { + let proto_directory = if std::fs::exists("../hello.proto").unwrap_or(false) { + let mut pathbuf = PathBuf::new(); + pathbuf.push(".."); + pathbuf + } else { + PathBuf::new() + }; + let proto_file = { + let mut pathbuf = proto_directory.clone(); + pathbuf.push("hello.proto"); + pathbuf + }; + configure().compile_protos(&[proto_file], &[proto_directory]).unwrap(); +} diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/src/main.rs b/dockertest/tests/grpcweb/grpcweb-test-proxy/src/main.rs new file mode 100644 index 00000000..9b914812 --- /dev/null +++ b/dockertest/tests/grpcweb/grpcweb-test-proxy/src/main.rs @@ -0,0 +1,84 @@ +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, + routing::post, + Json, Router, +}; +use hello_world::{greeter_client::GreeterClient, HelloRequest}; +use hyper_util::rt::TokioExecutor; +use tonic_web::GrpcWebClientLayer; + +mod hello_world { + tonic::include_proto!("helloworld"); +} + +#[derive(serde::Serialize)] +struct ErrorJson { + message: String, +} + +struct AppError(anyhow::Error); + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorJson { + message: self.0.to_string(), + }), + ) + .into_response() + } +} + +#[derive(serde::Deserialize)] +struct HelloRequestJson { + name: String, +} + +#[derive(serde::Serialize)] +struct HelloReplyJson { + message: String, +} + +async fn grpc_client(Json(request): Json) -> Result, AppError> { + let client = hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build_http(); + + let svc = tower::ServiceBuilder::new() + .layer(GrpcWebClientLayer::new()) + .service(client); + + // The client isn't kept alive, since this is just a test proxy, and it's not as performance-critical... + // This proxy is not intended to be used in production anyway... + let mut client = GreeterClient::with_origin( + svc, + std::env::var("GRPCWEB_TEST_PROXY_BACKEND_URL") + .unwrap_or("http://127.0.0.1:3000".to_string()) + .try_into() + .map_err(|e| AppError(anyhow::anyhow!("gRPC-Web error: {e}")))?, + ); + + let request = tonic::Request::new(HelloRequest { name: request.name }); + + let response = client + .say_hello(request) + .await + .map_err(|e| AppError(anyhow::anyhow!("gRPC-Web error: {e}")))?; + + let message = response.into_inner().message; + + Ok(Json(HelloReplyJson { message })) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let app = Router::new().route("/", post(grpc_client)); + + let listener = tokio::net::TcpListener::bind( + std::env::var("GRPCWEB_TEST_PROXY_LISTEN_ADDR").unwrap_or("0.0.0.0:8080".to_string()), + ) + .await?; + axum::serve(listener, app).await?; + + Ok(()) +} diff --git a/dockertest/tests/grpcweb/hello.proto b/dockertest/tests/grpcweb/hello.proto new file mode 100644 index 00000000..1270ae0e --- /dev/null +++ b/dockertest/tests/grpcweb/hello.proto @@ -0,0 +1,39 @@ +// Copied from https://github.com/hyperium/tonic/blob/115b95ceedbd58186d6c461b0a7f12e10353dbfd/examples/proto/helloworld/helloworld.proto + +// Copyright 2015 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/dockertest/tests/grpcweb/run.sh b/dockertest/tests/grpcweb/run.sh new file mode 100755 index 00000000..63668219 --- /dev/null +++ b/dockertest/tests/grpcweb/run.sh @@ -0,0 +1,13 @@ +#!/bin/bash +TEST_FAILED=0 +cp hello.proto backend/ +cp hello.proto grpcweb-test-proxy/ +docker compose --progress quiet up --build -d > /dev/null || (echo "Failed to start containers for a test suite" >&2; exit 1) +cat test.sh | docker compose --progress quiet exec -T test-runner bash 2>&1 || TEST_FAILED=1 +docker compose --progress quiet kill -s SIGKILL > /dev/null || true +docker compose --progress quiet down -v > /dev/null || true +rm -f backend/hello.proto +rm -f grpcweb-test-proxy/hello.proto +if [ "$TEST_FAILED" -eq 1 ]; then + exit 1 +fi diff --git a/dockertest/tests/grpcweb/test.sh b/dockertest/tests/grpcweb/test.sh new file mode 100755 index 00000000..f8e7c4a9 --- /dev/null +++ b/dockertest/tests/grpcweb/test.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +TEST_FAILED=0 + +# Wait for the backend server to start +for i in $(seq 1 3) +do + if [ "$i" -gt 1 ]; then + sleep 1 + fi + nc -z backend 50051 >/dev/null 2>&1 && break || true +done + +# Wait for the HTTP server to start +for i in $(seq 1 3) +do + if [ "$i" -gt 1 ]; then + sleep 1 + fi + nc -z ferron 80 >/dev/null 2>&1 && break || true +done + +# Wait for the test proxy server to start +for i in $(seq 1 3) +do + if [ "$i" -gt 1 ]; then + sleep 1 + fi + nc -z grpcweb-test-proxy 8080 >/dev/null 2>&1 && break || true +done + +TEST_RESULTS=$(curl -fsS -X POST -H "Content-Type: application/json" -d '{"name": "Ferron"}' http://grpcweb-test-proxy:8080 | jq .message) +TEST_EXPECTED='"Hello Ferron"' +TEST_EXIT_CODE=$? +if [ "$TEST_EXIT_CODE" -eq 0 ] && [ "$TEST_RESULTS" = "$TEST_EXPECTED" ]; then + echo "Basic gRPC-Web proxying test passed!" +else + echo "Basic gRPC-Web proxying test failed!" >&2 + echo " Exit code: $TEST_EXIT_CODE" >&2 + echo " Expected: $TEST_EXPECTED" >&2 + echo " Received: $TEST_RESULTS" >&2 + TEST_FAILED=1 +fi + +if [ "$TEST_FAILED" -eq 1 ]; then + exit 1 +fi From d513b0930902517f0c40b3730a6dd469c71a2cbe Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Fri, 23 Jan 2026 11:09:31 +0100 Subject: [PATCH 7/7] test: replace custom gRPC-Web proxy with `grpcweb-cli` for gRPC-Web tests --- dockertest/runner/Dockerfile | 21 + dockertest/tests/grpcweb/docker-compose.yml | 10 +- .../grpcweb/grpcweb-test-proxy/.dockerignore | 2 - .../grpcweb/grpcweb-test-proxy/.gitignore | 2 - .../grpcweb/grpcweb-test-proxy/Cargo.lock | 1194 ----------------- .../grpcweb/grpcweb-test-proxy/Cargo.toml | 22 - .../grpcweb/grpcweb-test-proxy/Dockerfile | 29 - .../tests/grpcweb/grpcweb-test-proxy/build.rs | 19 - .../grpcweb/grpcweb-test-proxy/src/main.rs | 84 -- dockertest/tests/grpcweb/run.sh | 2 - dockertest/tests/grpcweb/test.sh | 11 +- 11 files changed, 24 insertions(+), 1372 deletions(-) delete mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/.dockerignore delete mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/.gitignore delete mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.lock delete mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.toml delete mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/Dockerfile delete mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/build.rs delete mode 100644 dockertest/tests/grpcweb/grpcweb-test-proxy/src/main.rs diff --git a/dockertest/runner/Dockerfile b/dockertest/runner/Dockerfile index cedf5c7b..baabd534 100644 --- a/dockertest/runner/Dockerfile +++ b/dockertest/runner/Dockerfile @@ -30,6 +30,24 @@ RUN --mount=type=cache,sharing=private,target=/go/pkg/mod \ # Build grpcurl CGO_ENABLED=0 go build -o /usr/local/bin/grpcurl ./cmd/grpcurl +# Use Rust base image for building grpcweb-cli +FROM rust:trixie AS builder-grpcweb-cli + +# Set the working directory for grpcweb-cli +WORKDIR /usr/src/grpcweb-cli + +# Clone the grpcweb-cli Git repository +RUN git clone https://github.com/DorianNiemiecSVRJS/grpcweb-cli.git /usr/src/grpcweb-cli + +# Build grpcweb-cli +RUN --mount=type=cache,sharing=private,target=/usr/local/cargo/git \ + --mount=type=cache,sharing=private,target=/usr/local/cargo/registry \ + --mount=type=cache,sharing=private,target=/usr/src/grpcweb-cli/target \ + # Build grpcweb-cli + cargo build --release && \ + # Copy executables out of the cache + mkdir .dist && cp target/release/grpcweb-cli .dist + # Use Debian base image FROM debian:trixie @@ -47,3 +65,6 @@ COPY --from=builder-websocat /usr/src/websocat/.dist /usr/local/bin # Copy the built grpcurl COPY --from=builder-grpcurl /usr/local/bin/grpcurl /usr/local/bin + +# Copy the built grpcweb-cli +COPY --from=builder-grpcweb-cli /usr/src/grpcweb-cli/.dist /usr/local/bin diff --git a/dockertest/tests/grpcweb/docker-compose.yml b/dockertest/tests/grpcweb/docker-compose.yml index 200118ac..dd25b29b 100644 --- a/dockertest/tests/grpcweb/docker-compose.yml +++ b/dockertest/tests/grpcweb/docker-compose.yml @@ -12,16 +12,10 @@ services: build: context: ./backend - # The test proxy server for testing - grpcweb-test-proxy: - build: - context: ./grpcweb-test-proxy - environment: - GRPCWEB_TEST_PROXY_LISTEN_ADDR: "0.0.0.0:8080" - GRPCWEB_TEST_PROXY_BACKEND_URL: "http://ferron/" - # A container to run tests test-runner: build: context: ../../runner command: "tail -f /dev/null" + volumes: + - ./hello.proto:/tmp/hello.proto diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/.dockerignore b/dockertest/tests/grpcweb/grpcweb-test-proxy/.dockerignore deleted file mode 100644 index 4fffb2f8..00000000 --- a/dockertest/tests/grpcweb/grpcweb-test-proxy/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -/Cargo.lock diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/.gitignore b/dockertest/tests/grpcweb/grpcweb-test-proxy/.gitignore deleted file mode 100644 index 5939d4e3..00000000 --- a/dockertest/tests/grpcweb/grpcweb-test-proxy/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -/hello.proto diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.lock b/dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.lock deleted file mode 100644 index 5113f2f4..00000000 --- a/dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.lock +++ /dev/null @@ -1,1194 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "axum" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" -dependencies = [ - "axum-core", - "axum-macros", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "serde_core", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-macros" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "bytes" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "pin-utils", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - -[[package]] -name = "grpcweb-test-proxy" -version = "0.1.0" -dependencies = [ - "anyhow", - "axum", - "hyper-util", - "prost", - "serde", - "tokio", - "tonic", - "tonic-prost", - "tonic-prost-build", - "tonic-web", - "tower", -] - -[[package]] -name = "h2" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-timeout" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" -dependencies = [ - "hyper", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "libc", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "libc" -version = "0.2.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mio" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "multimap" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "petgraph" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" -dependencies = [ - "fixedbitset", - "hashbrown 0.15.5", - "indexmap", -] - -[[package]] -name = "pin-project" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" -dependencies = [ - "heck", - "itertools", - "log", - "multimap", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "pulldown-cmark", - "pulldown-cmark-to-cmark", - "regex", - "syn", - "tempfile", -] - -[[package]] -name = "prost-derive" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "prost-types" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" -dependencies = [ - "prost", -] - -[[package]] -name = "pulldown-cmark" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" -dependencies = [ - "bitflags", - "memchr", - "unicase", -] - -[[package]] -name = "pulldown-cmark-to-cmark" -version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" -dependencies = [ - "pulldown-cmark", -] - -[[package]] -name = "quote" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - -[[package]] -name = "rustix" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "ryu" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "syn" -version = "2.0.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" - -[[package]] -name = "tempfile" -version = "3.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" -dependencies = [ - "fastrand", - "getrandom", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio" -version = "1.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tonic" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" -dependencies = [ - "async-trait", - "base64", - "bytes", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-timeout", - "hyper-util", - "percent-encoding", - "pin-project", - "socket2", - "sync_wrapper", - "tokio", - "tokio-stream", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tonic-build" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40aaccc9f9eccf2cd82ebc111adc13030d23e887244bc9cfa5d1d636049de3" -dependencies = [ - "prettyplease", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tonic-prost" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" -dependencies = [ - "bytes", - "prost", - "tonic", -] - -[[package]] -name = "tonic-prost-build" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a16cba4043dc3ff43fcb3f96b4c5c154c64cbd18ca8dce2ab2c6a451d058a2" -dependencies = [ - "prettyplease", - "proc-macro2", - "prost-build", - "prost-types", - "quote", - "syn", - "tempfile", - "tonic-build", -] - -[[package]] -name = "tonic-web" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75214f6b6bd28c19aa752ac09fdf0eea546095670906c21fe3940e180a4c43f2" -dependencies = [ - "base64", - "bytes", - "http", - "http-body", - "pin-project", - "tokio-stream", - "tonic", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "indexmap", - "pin-project-lite", - "slab", - "sync_wrapper", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" - -[[package]] -name = "zmij" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.toml b/dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.toml deleted file mode 100644 index 73acf0ef..00000000 --- a/dockertest/tests/grpcweb/grpcweb-test-proxy/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "grpcweb-test-proxy" -version = "0.1.0" -edition = "2021" - -[dependencies] -hyper-util = { version = "0.1.19", features = ["client-legacy"] } -tokio = { version = "1.49.0", features = ["full"] } -tonic = { version = "0.14.2", default-features = false, features = ["transport", "codegen"] } -tonic-web = "0.14.2" -tower = "0.5.3" -prost = "0.14.3" -tonic-prost = "0.14.2" -axum = { version = "0.8.8", features = ["macros"] } -anyhow = "1.0.100" -serde = { version = "1.0.228", features = ["derive"] } - -[build-dependencies] -tonic-prost-build = "0.14.2" - -[package.metadata.cargo-shear] -ignored = ["tonic-prost", "prost"] diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/Dockerfile b/dockertest/tests/grpcweb/grpcweb-test-proxy/Dockerfile deleted file mode 100644 index 1d0ef2cd..00000000 --- a/dockertest/tests/grpcweb/grpcweb-test-proxy/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -# Use the official Rust image as a build stage -FROM rust:alpine AS builder - -# Set the working directory -WORKDIR /usr/src/grpcweb-test-proxy - -# Copy the source code -COPY . . - -# Install required packages -RUN apk add musl-dev protoc - -# Build the actual application (cache dependencies with BuildKit) -RUN --mount=type=cache,target=/usr/local/cargo/git \ - --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,sharing=private,target=/usr/src/grpcweb-test-proxy/target \ - # Debug build would work, since this is just a test proxy anyway... - cargo build && \ - # Copy executables out of the cache - mkdir .dist && cp target/debug/grpcweb-test-proxy .dist/grpcweb-test-proxy - -# Use alpine as the final stage -FROM alpine:latest - -# Copy the executable from the build stage -COPY --from=builder /usr/src/grpcweb-test-proxy/.dist/grpcweb-test-proxy /usr/local/bin/grpcweb-test-proxy - -# Set the entrypoint -ENTRYPOINT ["/usr/local/bin/grpcweb-test-proxy"] diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/build.rs b/dockertest/tests/grpcweb/grpcweb-test-proxy/build.rs deleted file mode 100644 index d664e057..00000000 --- a/dockertest/tests/grpcweb/grpcweb-test-proxy/build.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::path::PathBuf; - -use tonic_prost_build::configure; - -fn main() { - let proto_directory = if std::fs::exists("../hello.proto").unwrap_or(false) { - let mut pathbuf = PathBuf::new(); - pathbuf.push(".."); - pathbuf - } else { - PathBuf::new() - }; - let proto_file = { - let mut pathbuf = proto_directory.clone(); - pathbuf.push("hello.proto"); - pathbuf - }; - configure().compile_protos(&[proto_file], &[proto_directory]).unwrap(); -} diff --git a/dockertest/tests/grpcweb/grpcweb-test-proxy/src/main.rs b/dockertest/tests/grpcweb/grpcweb-test-proxy/src/main.rs deleted file mode 100644 index 9b914812..00000000 --- a/dockertest/tests/grpcweb/grpcweb-test-proxy/src/main.rs +++ /dev/null @@ -1,84 +0,0 @@ -use axum::{ - http::StatusCode, - response::{IntoResponse, Response}, - routing::post, - Json, Router, -}; -use hello_world::{greeter_client::GreeterClient, HelloRequest}; -use hyper_util::rt::TokioExecutor; -use tonic_web::GrpcWebClientLayer; - -mod hello_world { - tonic::include_proto!("helloworld"); -} - -#[derive(serde::Serialize)] -struct ErrorJson { - message: String, -} - -struct AppError(anyhow::Error); - -impl IntoResponse for AppError { - fn into_response(self) -> Response { - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorJson { - message: self.0.to_string(), - }), - ) - .into_response() - } -} - -#[derive(serde::Deserialize)] -struct HelloRequestJson { - name: String, -} - -#[derive(serde::Serialize)] -struct HelloReplyJson { - message: String, -} - -async fn grpc_client(Json(request): Json) -> Result, AppError> { - let client = hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build_http(); - - let svc = tower::ServiceBuilder::new() - .layer(GrpcWebClientLayer::new()) - .service(client); - - // The client isn't kept alive, since this is just a test proxy, and it's not as performance-critical... - // This proxy is not intended to be used in production anyway... - let mut client = GreeterClient::with_origin( - svc, - std::env::var("GRPCWEB_TEST_PROXY_BACKEND_URL") - .unwrap_or("http://127.0.0.1:3000".to_string()) - .try_into() - .map_err(|e| AppError(anyhow::anyhow!("gRPC-Web error: {e}")))?, - ); - - let request = tonic::Request::new(HelloRequest { name: request.name }); - - let response = client - .say_hello(request) - .await - .map_err(|e| AppError(anyhow::anyhow!("gRPC-Web error: {e}")))?; - - let message = response.into_inner().message; - - Ok(Json(HelloReplyJson { message })) -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - let app = Router::new().route("/", post(grpc_client)); - - let listener = tokio::net::TcpListener::bind( - std::env::var("GRPCWEB_TEST_PROXY_LISTEN_ADDR").unwrap_or("0.0.0.0:8080".to_string()), - ) - .await?; - axum::serve(listener, app).await?; - - Ok(()) -} diff --git a/dockertest/tests/grpcweb/run.sh b/dockertest/tests/grpcweb/run.sh index 63668219..76454b21 100755 --- a/dockertest/tests/grpcweb/run.sh +++ b/dockertest/tests/grpcweb/run.sh @@ -1,13 +1,11 @@ #!/bin/bash TEST_FAILED=0 cp hello.proto backend/ -cp hello.proto grpcweb-test-proxy/ docker compose --progress quiet up --build -d > /dev/null || (echo "Failed to start containers for a test suite" >&2; exit 1) cat test.sh | docker compose --progress quiet exec -T test-runner bash 2>&1 || TEST_FAILED=1 docker compose --progress quiet kill -s SIGKILL > /dev/null || true docker compose --progress quiet down -v > /dev/null || true rm -f backend/hello.proto -rm -f grpcweb-test-proxy/hello.proto if [ "$TEST_FAILED" -eq 1 ]; then exit 1 fi diff --git a/dockertest/tests/grpcweb/test.sh b/dockertest/tests/grpcweb/test.sh index f8e7c4a9..5fda8248 100755 --- a/dockertest/tests/grpcweb/test.sh +++ b/dockertest/tests/grpcweb/test.sh @@ -20,16 +20,7 @@ do nc -z ferron 80 >/dev/null 2>&1 && break || true done -# Wait for the test proxy server to start -for i in $(seq 1 3) -do - if [ "$i" -gt 1 ]; then - sleep 1 - fi - nc -z grpcweb-test-proxy 8080 >/dev/null 2>&1 && break || true -done - -TEST_RESULTS=$(curl -fsS -X POST -H "Content-Type: application/json" -d '{"name": "Ferron"}' http://grpcweb-test-proxy:8080 | jq .message) +TEST_RESULTS="$(grpcweb-cli --data '{"name": "Ferron"}' --include /tmp --proto /tmp/hello.proto --url http://ferron/helloworld.Greeter/SayHello | jq .message)" TEST_EXPECTED='"Hello Ferron"' TEST_EXIT_CODE=$? if [ "$TEST_EXIT_CODE" -eq 0 ] && [ "$TEST_RESULTS" = "$TEST_EXPECTED" ]; then