spikard 0.13.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Steepfile +6 -0
- data/ext/spikard_rb/extconf.rb +1 -2
- data/ext/spikard_rb/{Cargo.lock → native/Cargo.lock} +819 -424
- data/ext/spikard_rb/native/Cargo.toml +24 -0
- data/ext/spikard_rb/src/lib.rs +5366 -3
- data/lib/spikard/native.rb +86 -0
- data/lib/spikard/version.rb +6 -1
- data/lib/spikard.rb +8 -52
- data/lib/spikard_rb.so +0 -0
- data/sig/types.rbs +427 -0
- metadata +14 -243
- data/LICENSE +0 -1
- data/README.md +0 -285
- data/ext/spikard_rb/Cargo.toml +0 -17
- data/lib/spikard/app.rb +0 -458
- data/lib/spikard/background.rb +0 -58
- data/lib/spikard/config.rb +0 -506
- data/lib/spikard/converters.rb +0 -13
- data/lib/spikard/grpc.rb +0 -232
- data/lib/spikard/handler_wrapper.rb +0 -113
- data/lib/spikard/provide.rb +0 -315
- data/lib/spikard/response.rb +0 -198
- data/lib/spikard/schema.rb +0 -243
- data/lib/spikard/sse.rb +0 -111
- data/lib/spikard/streaming_response.rb +0 -44
- data/lib/spikard/testing.rb +0 -474
- data/lib/spikard/upload_file.rb +0 -131
- data/lib/spikard/websocket.rb +0 -59
- data/sig/spikard.rbs +0 -739
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +0 -75
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +0 -132
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +0 -905
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +0 -210
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +0 -252
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +0 -404
- data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +0 -199
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +0 -252
- data/vendor/crates/spikard-bindings-shared/src/json_conversion.rs +0 -829
- data/vendor/crates/spikard-bindings-shared/src/lazy_cache.rs +0 -587
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +0 -33
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +0 -298
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +0 -594
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +0 -743
- data/vendor/crates/spikard-bindings-shared/src/response_interpreter.rs +0 -944
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +0 -260
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +0 -369
- data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +0 -192
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +0 -383
- data/vendor/crates/spikard-bindings-shared/tests/full_coverage.rs +0 -459
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +0 -280
- data/vendor/crates/spikard-bindings-shared/tests/integration_tests.rs +0 -669
- data/vendor/crates/spikard-core/Cargo.toml +0 -55
- data/vendor/crates/spikard-core/src/bindings/mod.rs +0 -3
- data/vendor/crates/spikard-core/src/bindings/response.rs +0 -130
- data/vendor/crates/spikard-core/src/debug.rs +0 -127
- data/vendor/crates/spikard-core/src/di/container.rs +0 -711
- data/vendor/crates/spikard-core/src/di/dependency.rs +0 -273
- data/vendor/crates/spikard-core/src/di/error.rs +0 -118
- data/vendor/crates/spikard-core/src/di/factory.rs +0 -548
- data/vendor/crates/spikard-core/src/di/graph.rs +0 -507
- data/vendor/crates/spikard-core/src/di/mod.rs +0 -192
- data/vendor/crates/spikard-core/src/di/resolved.rs +0 -428
- data/vendor/crates/spikard-core/src/di/value.rs +0 -282
- data/vendor/crates/spikard-core/src/errors.rs +0 -72
- data/vendor/crates/spikard-core/src/http.rs +0 -492
- data/vendor/crates/spikard-core/src/lib.rs +0 -29
- data/vendor/crates/spikard-core/src/lifecycle.rs +0 -1273
- data/vendor/crates/spikard-core/src/metadata.rs +0 -378
- data/vendor/crates/spikard-core/src/parameters.rs +0 -2546
- data/vendor/crates/spikard-core/src/problem.rs +0 -358
- data/vendor/crates/spikard-core/src/request_data.rs +0 -1146
- data/vendor/crates/spikard-core/src/router.rs +0 -530
- data/vendor/crates/spikard-core/src/schema_registry.rs +0 -197
- data/vendor/crates/spikard-core/src/type_hints.rs +0 -311
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +0 -710
- data/vendor/crates/spikard-core/src/validation/mod.rs +0 -470
- data/vendor/crates/spikard-core/tests/bindings_response_tests.rs +0 -136
- data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +0 -37
- data/vendor/crates/spikard-core/tests/error_mapper.rs +0 -761
- data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +0 -106
- data/vendor/crates/spikard-core/tests/parameters_full.rs +0 -701
- data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +0 -301
- data/vendor/crates/spikard-core/tests/request_data_roundtrip.rs +0 -67
- data/vendor/crates/spikard-core/tests/validation_coverage.rs +0 -250
- data/vendor/crates/spikard-core/tests/validation_error_paths.rs +0 -45
- data/vendor/crates/spikard-http/Cargo.toml +0 -82
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +0 -148
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +0 -92
- data/vendor/crates/spikard-http/src/auth.rs +0 -301
- data/vendor/crates/spikard-http/src/background.rs +0 -1859
- data/vendor/crates/spikard-http/src/bindings/mod.rs +0 -3
- data/vendor/crates/spikard-http/src/bindings/response.rs +0 -1
- data/vendor/crates/spikard-http/src/body_metadata.rs +0 -8
- data/vendor/crates/spikard-http/src/cors.rs +0 -1026
- data/vendor/crates/spikard-http/src/debug.rs +0 -128
- data/vendor/crates/spikard-http/src/di_handler.rs +0 -1672
- data/vendor/crates/spikard-http/src/grpc/framing.rs +0 -653
- data/vendor/crates/spikard-http/src/grpc/handler.rs +0 -1211
- data/vendor/crates/spikard-http/src/grpc/mod.rs +0 -556
- data/vendor/crates/spikard-http/src/grpc/service.rs +0 -706
- data/vendor/crates/spikard-http/src/grpc/streaming.rs +0 -319
- data/vendor/crates/spikard-http/src/handler_response.rs +0 -901
- data/vendor/crates/spikard-http/src/handler_trait.rs +0 -1015
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +0 -290
- data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +0 -502
- data/vendor/crates/spikard-http/src/jsonrpc/method_registry.rs +0 -648
- data/vendor/crates/spikard-http/src/jsonrpc/mod.rs +0 -60
- data/vendor/crates/spikard-http/src/jsonrpc/openrpc.rs +0 -325
- data/vendor/crates/spikard-http/src/jsonrpc/protocol.rs +0 -1207
- data/vendor/crates/spikard-http/src/jsonrpc/router.rs +0 -2262
- data/vendor/crates/spikard-http/src/lib.rs +0 -566
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +0 -230
- data/vendor/crates/spikard-http/src/lifecycle.rs +0 -1193
- data/vendor/crates/spikard-http/src/middleware/mod.rs +0 -560
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +0 -912
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +0 -513
- data/vendor/crates/spikard-http/src/middleware/validation.rs +0 -768
- data/vendor/crates/spikard-http/src/openapi/mod.rs +0 -309
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +0 -535
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +0 -1363
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +0 -667
- data/vendor/crates/spikard-http/src/query_parser.rs +0 -793
- data/vendor/crates/spikard-http/src/response.rs +0 -720
- data/vendor/crates/spikard-http/src/server/fast_router.rs +0 -186
- data/vendor/crates/spikard-http/src/server/grpc_routing.rs +0 -1243
- data/vendor/crates/spikard-http/src/server/handler.rs +0 -1661
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +0 -253
- data/vendor/crates/spikard-http/src/server/mod.rs +0 -1717
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +0 -871
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +0 -618
- data/vendor/crates/spikard-http/src/sse.rs +0 -1409
- data/vendor/crates/spikard-http/src/testing/form.rs +0 -52
- data/vendor/crates/spikard-http/src/testing/multipart.rs +0 -64
- data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -825
- data/vendor/crates/spikard-http/src/testing.rs +0 -617
- data/vendor/crates/spikard-http/src/websocket.rs +0 -1477
- data/vendor/crates/spikard-http/tests/auth_integration.rs +0 -645
- data/vendor/crates/spikard-http/tests/background_behavior.rs +0 -832
- data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +0 -1012
- data/vendor/crates/spikard-http/tests/common/handlers.rs +0 -309
- data/vendor/crates/spikard-http/tests/common/mod.rs +0 -33
- data/vendor/crates/spikard-http/tests/common/test_builders.rs +0 -628
- data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +0 -162
- data/vendor/crates/spikard-http/tests/di_integration.rs +0 -192
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +0 -5
- data/vendor/crates/spikard-http/tests/grpc_bidirectional_streaming.rs +0 -430
- data/vendor/crates/spikard-http/tests/grpc_client_streaming.rs +0 -738
- data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +0 -652
- data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +0 -334
- data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +0 -532
- data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +0 -495
- data/vendor/crates/spikard-http/tests/grpc_server_streaming.rs +0 -975
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +0 -1093
- data/vendor/crates/spikard-http/tests/middleware_stack_integration.rs +0 -389
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +0 -656
- data/vendor/crates/spikard-http/tests/request_extraction_full.rs +0 -513
- data/vendor/crates/spikard-http/tests/server_auth_middleware_behavior.rs +0 -328
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +0 -335
- data/vendor/crates/spikard-http/tests/server_configured_router_behavior.rs +0 -374
- data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +0 -83
- data/vendor/crates/spikard-http/tests/server_handler_wrappers.rs +0 -464
- data/vendor/crates/spikard-http/tests/server_method_router_additional_behavior.rs +0 -286
- data/vendor/crates/spikard-http/tests/server_method_router_coverage.rs +0 -118
- data/vendor/crates/spikard-http/tests/server_middleware_behavior.rs +0 -99
- data/vendor/crates/spikard-http/tests/server_middleware_branches.rs +0 -204
- data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +0 -427
- data/vendor/crates/spikard-http/tests/server_router_behavior.rs +0 -121
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +0 -620
- data/vendor/crates/spikard-http/tests/sse_full_behavior.rs +0 -584
- data/vendor/crates/spikard-http/tests/sse_handler_behavior.rs +0 -130
- data/vendor/crates/spikard-http/tests/test_client_requests.rs +0 -167
- data/vendor/crates/spikard-http/tests/testing_helpers.rs +0 -87
- data/vendor/crates/spikard-http/tests/testing_module_coverage.rs +0 -155
- data/vendor/crates/spikard-http/tests/urlencoded_content_type.rs +0 -82
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +0 -663
- data/vendor/crates/spikard-http/tests/websocket_full_behavior.rs +0 -440
- data/vendor/crates/spikard-http/tests/websocket_integration.rs +0 -150
- data/vendor/crates/spikard-rb/Cargo.toml +0 -63
- data/vendor/crates/spikard-rb/build.rs +0 -200
- data/vendor/crates/spikard-rb/src/background.rs +0 -63
- data/vendor/crates/spikard-rb/src/config/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/config/server_config.rs +0 -401
- data/vendor/crates/spikard-rb/src/conversion.rs +0 -688
- data/vendor/crates/spikard-rb/src/di/builder.rs +0 -100
- data/vendor/crates/spikard-rb/src/di/mod.rs +0 -410
- data/vendor/crates/spikard-rb/src/grpc/handler.rs +0 -875
- data/vendor/crates/spikard-rb/src/grpc/mod.rs +0 -13
- data/vendor/crates/spikard-rb/src/gvl.rs +0 -80
- data/vendor/crates/spikard-rb/src/handler.rs +0 -699
- data/vendor/crates/spikard-rb/src/integration/mod.rs +0 -3
- data/vendor/crates/spikard-rb/src/lib.rs +0 -2268
- data/vendor/crates/spikard-rb/src/lifecycle.rs +0 -334
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +0 -507
- data/vendor/crates/spikard-rb/src/request.rs +0 -439
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +0 -368
- data/vendor/crates/spikard-rb/src/server.rs +0 -304
- data/vendor/crates/spikard-rb/src/sse.rs +0 -231
- data/vendor/crates/spikard-rb/src/testing/client.rs +0 -698
- data/vendor/crates/spikard-rb/src/testing/mod.rs +0 -7
- data/vendor/crates/spikard-rb/src/testing/sse.rs +0 -108
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +0 -573
- data/vendor/crates/spikard-rb/src/websocket.rs +0 -521
- data/vendor/crates/spikard-rb-macros/Cargo.toml +0 -20
- data/vendor/crates/spikard-rb-macros/src/lib.rs +0 -51
|
@@ -1,1243 +0,0 @@
|
|
|
1
|
-
//! gRPC request routing and multiplexing
|
|
2
|
-
//!
|
|
3
|
-
//! This module handles routing gRPC requests to the appropriate handlers
|
|
4
|
-
//! and multiplexing between HTTP/1.1 REST and HTTP/2 gRPC traffic.
|
|
5
|
-
|
|
6
|
-
use crate::grpc::framing::{GRPC_MESSAGE_HEADER_LEN, encode_grpc_message, parse_unary_grpc_message};
|
|
7
|
-
use crate::grpc::{GrpcConfig, GrpcRegistry, RpcMode, parse_grpc_path};
|
|
8
|
-
use axum::body::Body;
|
|
9
|
-
use axum::http::{Request, Response, StatusCode};
|
|
10
|
-
use std::sync::Arc;
|
|
11
|
-
|
|
12
|
-
/// Convert gRPC status code to HTTP status code
|
|
13
|
-
///
|
|
14
|
-
/// Maps all gRPC status codes to appropriate HTTP status codes
|
|
15
|
-
/// following the gRPC-HTTP status code mapping specification.
|
|
16
|
-
fn grpc_status_to_http(code: tonic::Code) -> StatusCode {
|
|
17
|
-
match code {
|
|
18
|
-
tonic::Code::Ok => StatusCode::OK,
|
|
19
|
-
tonic::Code::Cancelled => StatusCode::from_u16(499).unwrap(), // Client Closed Request
|
|
20
|
-
tonic::Code::Unknown => StatusCode::INTERNAL_SERVER_ERROR,
|
|
21
|
-
tonic::Code::InvalidArgument => StatusCode::BAD_REQUEST,
|
|
22
|
-
tonic::Code::DeadlineExceeded => StatusCode::GATEWAY_TIMEOUT,
|
|
23
|
-
tonic::Code::NotFound => StatusCode::NOT_FOUND,
|
|
24
|
-
tonic::Code::AlreadyExists => StatusCode::CONFLICT,
|
|
25
|
-
tonic::Code::PermissionDenied => StatusCode::FORBIDDEN,
|
|
26
|
-
tonic::Code::ResourceExhausted => StatusCode::TOO_MANY_REQUESTS,
|
|
27
|
-
tonic::Code::FailedPrecondition => StatusCode::BAD_REQUEST,
|
|
28
|
-
tonic::Code::Aborted => StatusCode::CONFLICT,
|
|
29
|
-
tonic::Code::OutOfRange => StatusCode::BAD_REQUEST,
|
|
30
|
-
tonic::Code::Unimplemented => StatusCode::NOT_IMPLEMENTED,
|
|
31
|
-
tonic::Code::Internal => StatusCode::INTERNAL_SERVER_ERROR,
|
|
32
|
-
tonic::Code::Unavailable => StatusCode::SERVICE_UNAVAILABLE,
|
|
33
|
-
tonic::Code::DataLoss => StatusCode::INTERNAL_SERVER_ERROR,
|
|
34
|
-
tonic::Code::Unauthenticated => StatusCode::UNAUTHORIZED,
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/// Route a gRPC request to the appropriate handler
|
|
39
|
-
///
|
|
40
|
-
/// Parses the request path to extract service and method names,
|
|
41
|
-
/// looks up the handler in the registry, and invokes it.
|
|
42
|
-
///
|
|
43
|
-
/// # Arguments
|
|
44
|
-
///
|
|
45
|
-
/// * `registry` - gRPC handler registry
|
|
46
|
-
/// * `config` - gRPC configuration with size limits
|
|
47
|
-
/// * `request` - The incoming gRPC request
|
|
48
|
-
///
|
|
49
|
-
/// # Returns
|
|
50
|
-
///
|
|
51
|
-
/// A future that resolves to a gRPC response or error
|
|
52
|
-
pub async fn route_grpc_request(
|
|
53
|
-
registry: Arc<GrpcRegistry>,
|
|
54
|
-
config: &GrpcConfig,
|
|
55
|
-
request: Request<Body>,
|
|
56
|
-
) -> Result<Response<Body>, (StatusCode, String)> {
|
|
57
|
-
// Extract the request path
|
|
58
|
-
let path = request.uri().path();
|
|
59
|
-
|
|
60
|
-
// Parse service and method names from the path
|
|
61
|
-
let (service_name, method_name) = match parse_grpc_path(path) {
|
|
62
|
-
Ok(names) => names,
|
|
63
|
-
Err(status) => {
|
|
64
|
-
return Err((
|
|
65
|
-
StatusCode::BAD_REQUEST,
|
|
66
|
-
format!("Invalid gRPC path: {}", status.message()),
|
|
67
|
-
));
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// Look up the handler for this service and method
|
|
72
|
-
let (handler, rpc_mode) = match registry.get(&service_name, &method_name) {
|
|
73
|
-
Some((h, mode)) => (h, mode),
|
|
74
|
-
None => {
|
|
75
|
-
let message = if registry.contains_service(&service_name) {
|
|
76
|
-
format!("Method not found: {service_name}/{method_name}")
|
|
77
|
-
} else {
|
|
78
|
-
format!("Service not found: {service_name}")
|
|
79
|
-
};
|
|
80
|
-
return Err((StatusCode::NOT_FOUND, message));
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
// Create the service bridge
|
|
85
|
-
let service = crate::grpc::GenericGrpcService::new(handler);
|
|
86
|
-
|
|
87
|
-
// Dispatch based on RPC mode
|
|
88
|
-
match rpc_mode {
|
|
89
|
-
RpcMode::Unary => {
|
|
90
|
-
handle_unary_request(
|
|
91
|
-
service,
|
|
92
|
-
service_name,
|
|
93
|
-
method_name,
|
|
94
|
-
config.max_message_size,
|
|
95
|
-
config.enable_compression,
|
|
96
|
-
request,
|
|
97
|
-
)
|
|
98
|
-
.await
|
|
99
|
-
}
|
|
100
|
-
RpcMode::ServerStreaming => {
|
|
101
|
-
handle_server_streaming_request(
|
|
102
|
-
service,
|
|
103
|
-
service_name,
|
|
104
|
-
method_name,
|
|
105
|
-
config.max_message_size,
|
|
106
|
-
config.enable_compression,
|
|
107
|
-
request,
|
|
108
|
-
)
|
|
109
|
-
.await
|
|
110
|
-
}
|
|
111
|
-
RpcMode::ClientStreaming => {
|
|
112
|
-
handle_client_streaming_request(
|
|
113
|
-
service,
|
|
114
|
-
service_name,
|
|
115
|
-
method_name,
|
|
116
|
-
config.max_message_size,
|
|
117
|
-
config.enable_compression,
|
|
118
|
-
request,
|
|
119
|
-
)
|
|
120
|
-
.await
|
|
121
|
-
}
|
|
122
|
-
RpcMode::BidirectionalStreaming => {
|
|
123
|
-
handle_bidirectional_streaming_request(
|
|
124
|
-
service,
|
|
125
|
-
service_name,
|
|
126
|
-
method_name,
|
|
127
|
-
config.max_message_size,
|
|
128
|
-
config.enable_compression,
|
|
129
|
-
request,
|
|
130
|
-
)
|
|
131
|
-
.await
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
fn unary_message_read_limit(max_message_size: usize) -> usize {
|
|
137
|
-
max_message_size.saturating_add(GRPC_MESSAGE_HEADER_LEN)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/// Handle a unary RPC request
|
|
141
|
-
async fn handle_unary_request(
|
|
142
|
-
service: crate::grpc::GenericGrpcService,
|
|
143
|
-
service_name: String,
|
|
144
|
-
method_name: String,
|
|
145
|
-
max_message_size: usize,
|
|
146
|
-
compression_enabled: bool,
|
|
147
|
-
request: Request<Body>,
|
|
148
|
-
) -> Result<Response<Body>, (StatusCode, String)> {
|
|
149
|
-
// Convert the Axum request to bytes with the configured size limit
|
|
150
|
-
let (parts, body) = request.into_parts();
|
|
151
|
-
let request_encoding = parts.headers.get("grpc-encoding").and_then(|value| value.to_str().ok());
|
|
152
|
-
let body_bytes = match axum::body::to_bytes(body, unary_message_read_limit(max_message_size)).await {
|
|
153
|
-
Ok(bytes) => bytes,
|
|
154
|
-
Err(e) => {
|
|
155
|
-
// Check if error is due to size limit (axum returns "body size exceeded" or similar)
|
|
156
|
-
let error_msg = e.to_string();
|
|
157
|
-
if error_msg.contains("body") || error_msg.contains("size") || error_msg.contains("exceeded") {
|
|
158
|
-
return Err((
|
|
159
|
-
StatusCode::PAYLOAD_TOO_LARGE,
|
|
160
|
-
format!("Message exceeds maximum size of {} bytes", max_message_size),
|
|
161
|
-
));
|
|
162
|
-
}
|
|
163
|
-
return Err((StatusCode::BAD_REQUEST, format!("Failed to read request body: {}", e)));
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
let payload = match parse_unary_grpc_message(&body_bytes, max_message_size, request_encoding, compression_enabled) {
|
|
168
|
-
Ok(message) => message,
|
|
169
|
-
Err(status) => {
|
|
170
|
-
let status_code = grpc_status_to_http(status.code());
|
|
171
|
-
return Err((status_code, status.message().to_string()));
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
// Create a Tonic request
|
|
176
|
-
let mut tonic_request = tonic::Request::new(payload);
|
|
177
|
-
|
|
178
|
-
// Copy headers to Tonic metadata
|
|
179
|
-
for (key, value) in parts.headers.iter() {
|
|
180
|
-
if let Ok(value_str) = value.to_str()
|
|
181
|
-
&& let Ok(metadata_value) = value_str.parse::<tonic::metadata::MetadataValue<tonic::metadata::Ascii>>()
|
|
182
|
-
&& let Ok(metadata_key) = key
|
|
183
|
-
.as_str()
|
|
184
|
-
.parse::<tonic::metadata::MetadataKey<tonic::metadata::Ascii>>()
|
|
185
|
-
{
|
|
186
|
-
tonic_request.metadata_mut().insert(metadata_key, metadata_value);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Use the service bridge to handle the request
|
|
191
|
-
let tonic_response = match service.handle_unary(service_name, method_name, tonic_request).await {
|
|
192
|
-
Ok(resp) => resp,
|
|
193
|
-
Err(status) => {
|
|
194
|
-
let status_code = grpc_status_to_http(status.code());
|
|
195
|
-
return Err((status_code, status.message().to_string()));
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
// Convert Tonic response to Axum response
|
|
200
|
-
let payload = tonic_response.get_ref().clone();
|
|
201
|
-
let framed_payload = match encode_grpc_message(payload) {
|
|
202
|
-
Ok(framed) => framed,
|
|
203
|
-
Err(status) => {
|
|
204
|
-
let status_code = grpc_status_to_http(status.code());
|
|
205
|
-
return Err((status_code, status.message().to_string()));
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
let metadata = tonic_response.metadata();
|
|
209
|
-
|
|
210
|
-
let mut response = Response::builder()
|
|
211
|
-
.status(StatusCode::OK)
|
|
212
|
-
.header("content-type", "application/grpc+proto");
|
|
213
|
-
|
|
214
|
-
// Copy metadata to response headers
|
|
215
|
-
for key_value in metadata.iter() {
|
|
216
|
-
if let tonic::metadata::KeyAndValueRef::Ascii(key, value) = key_value
|
|
217
|
-
&& let Ok(header_value) = axum::http::HeaderValue::from_str(value.to_str().unwrap_or(""))
|
|
218
|
-
{
|
|
219
|
-
response = response.header(key.as_str(), header_value);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Add gRPC status trailer (success)
|
|
224
|
-
response = response.header("grpc-status", "0");
|
|
225
|
-
|
|
226
|
-
// Convert bytes::Bytes to Body
|
|
227
|
-
let response = response.body(Body::from(framed_payload)).map_err(|e| {
|
|
228
|
-
(
|
|
229
|
-
StatusCode::INTERNAL_SERVER_ERROR,
|
|
230
|
-
format!("Failed to build response: {}", e),
|
|
231
|
-
)
|
|
232
|
-
})?;
|
|
233
|
-
|
|
234
|
-
Ok(response)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/// Handle a server streaming RPC request
|
|
238
|
-
async fn handle_server_streaming_request(
|
|
239
|
-
service: crate::grpc::GenericGrpcService,
|
|
240
|
-
service_name: String,
|
|
241
|
-
method_name: String,
|
|
242
|
-
max_message_size: usize,
|
|
243
|
-
compression_enabled: bool,
|
|
244
|
-
request: Request<Body>,
|
|
245
|
-
) -> Result<Response<Body>, (StatusCode, String)> {
|
|
246
|
-
// Convert the Axum request to bytes with the configured size limit
|
|
247
|
-
let (parts, body) = request.into_parts();
|
|
248
|
-
let request_encoding = parts.headers.get("grpc-encoding").and_then(|value| value.to_str().ok());
|
|
249
|
-
let body_bytes = match axum::body::to_bytes(body, unary_message_read_limit(max_message_size)).await {
|
|
250
|
-
Ok(bytes) => bytes,
|
|
251
|
-
Err(e) => {
|
|
252
|
-
// Check if error is due to size limit (axum returns "body size exceeded" or similar)
|
|
253
|
-
let error_msg = e.to_string();
|
|
254
|
-
if error_msg.contains("body") || error_msg.contains("size") || error_msg.contains("exceeded") {
|
|
255
|
-
return Err((
|
|
256
|
-
StatusCode::PAYLOAD_TOO_LARGE,
|
|
257
|
-
format!("Message exceeds maximum size of {} bytes", max_message_size),
|
|
258
|
-
));
|
|
259
|
-
}
|
|
260
|
-
return Err((StatusCode::BAD_REQUEST, format!("Failed to read request body: {}", e)));
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
let payload = match parse_unary_grpc_message(&body_bytes, max_message_size, request_encoding, compression_enabled) {
|
|
265
|
-
Ok(message) => message,
|
|
266
|
-
Err(status) => {
|
|
267
|
-
let status_code = grpc_status_to_http(status.code());
|
|
268
|
-
return Err((status_code, status.message().to_string()));
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
// Create a Tonic request
|
|
273
|
-
let mut tonic_request = tonic::Request::new(payload);
|
|
274
|
-
|
|
275
|
-
// Copy headers to Tonic metadata
|
|
276
|
-
for (key, value) in parts.headers.iter() {
|
|
277
|
-
if let Ok(value_str) = value.to_str()
|
|
278
|
-
&& let Ok(metadata_value) = value_str.parse::<tonic::metadata::MetadataValue<tonic::metadata::Ascii>>()
|
|
279
|
-
&& let Ok(metadata_key) = key
|
|
280
|
-
.as_str()
|
|
281
|
-
.parse::<tonic::metadata::MetadataKey<tonic::metadata::Ascii>>()
|
|
282
|
-
{
|
|
283
|
-
tonic_request.metadata_mut().insert(metadata_key, metadata_value);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Use the service bridge to handle the streaming request
|
|
288
|
-
let tonic_response = match service
|
|
289
|
-
.handle_server_stream(service_name, method_name, tonic_request)
|
|
290
|
-
.await
|
|
291
|
-
{
|
|
292
|
-
Ok(resp) => resp,
|
|
293
|
-
Err(status) => {
|
|
294
|
-
let status_code = grpc_status_to_http(status.code());
|
|
295
|
-
return Err((status_code, status.message().to_string()));
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
// Convert Tonic response to Axum response
|
|
300
|
-
let body = tonic_response.into_inner();
|
|
301
|
-
let response = Response::builder()
|
|
302
|
-
.status(StatusCode::OK)
|
|
303
|
-
.header("content-type", "application/grpc+proto");
|
|
304
|
-
|
|
305
|
-
let response = response.body(body).map_err(|e| {
|
|
306
|
-
(
|
|
307
|
-
StatusCode::INTERNAL_SERVER_ERROR,
|
|
308
|
-
format!("Failed to build response: {}", e),
|
|
309
|
-
)
|
|
310
|
-
})?;
|
|
311
|
-
|
|
312
|
-
Ok(response)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/// Handle a client streaming RPC request
|
|
316
|
-
async fn handle_client_streaming_request(
|
|
317
|
-
service: crate::grpc::GenericGrpcService,
|
|
318
|
-
service_name: String,
|
|
319
|
-
method_name: String,
|
|
320
|
-
max_message_size: usize,
|
|
321
|
-
compression_enabled: bool,
|
|
322
|
-
request: Request<Body>,
|
|
323
|
-
) -> Result<Response<Body>, (StatusCode, String)> {
|
|
324
|
-
// Extract request parts - keep body as stream for frame parsing
|
|
325
|
-
let (parts, body) = request.into_parts();
|
|
326
|
-
|
|
327
|
-
// Create a Tonic request with streaming body
|
|
328
|
-
// Body will be parsed by service.handle_client_stream using frame parser
|
|
329
|
-
let mut tonic_request = tonic::Request::new(body);
|
|
330
|
-
|
|
331
|
-
// Copy headers to Tonic metadata
|
|
332
|
-
for (key, value) in parts.headers.iter() {
|
|
333
|
-
if let Ok(value_str) = value.to_str()
|
|
334
|
-
&& let Ok(metadata_value) = value_str.parse::<tonic::metadata::MetadataValue<tonic::metadata::Ascii>>()
|
|
335
|
-
&& let Ok(metadata_key) = key
|
|
336
|
-
.as_str()
|
|
337
|
-
.parse::<tonic::metadata::MetadataKey<tonic::metadata::Ascii>>()
|
|
338
|
-
{
|
|
339
|
-
tonic_request.metadata_mut().insert(metadata_key, metadata_value);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Use the service bridge to handle the client streaming request
|
|
344
|
-
// Frame parsing and size validation happens in handle_client_stream
|
|
345
|
-
let tonic_response = match service
|
|
346
|
-
.handle_client_stream(
|
|
347
|
-
service_name,
|
|
348
|
-
method_name,
|
|
349
|
-
tonic_request,
|
|
350
|
-
max_message_size,
|
|
351
|
-
compression_enabled,
|
|
352
|
-
)
|
|
353
|
-
.await
|
|
354
|
-
{
|
|
355
|
-
Ok(resp) => resp,
|
|
356
|
-
Err(status) => {
|
|
357
|
-
let status_code = grpc_status_to_http(status.code());
|
|
358
|
-
return Err((status_code, status.message().to_string()));
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
// Convert Tonic response to Axum response
|
|
363
|
-
let payload = tonic_response.get_ref().clone();
|
|
364
|
-
let framed_payload = match encode_grpc_message(payload) {
|
|
365
|
-
Ok(framed) => framed,
|
|
366
|
-
Err(status) => {
|
|
367
|
-
let status_code = grpc_status_to_http(status.code());
|
|
368
|
-
return Err((status_code, status.message().to_string()));
|
|
369
|
-
}
|
|
370
|
-
};
|
|
371
|
-
let metadata = tonic_response.metadata();
|
|
372
|
-
|
|
373
|
-
let mut response = Response::builder()
|
|
374
|
-
.status(StatusCode::OK)
|
|
375
|
-
.header("content-type", "application/grpc+proto");
|
|
376
|
-
|
|
377
|
-
// Copy metadata to response headers
|
|
378
|
-
for key_value in metadata.iter() {
|
|
379
|
-
if let tonic::metadata::KeyAndValueRef::Ascii(key, value) = key_value
|
|
380
|
-
&& let Ok(header_value) = axum::http::HeaderValue::from_str(value.to_str().unwrap_or(""))
|
|
381
|
-
{
|
|
382
|
-
response = response.header(key.as_str(), header_value);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Add gRPC status trailer (success)
|
|
387
|
-
response = response.header("grpc-status", "0");
|
|
388
|
-
|
|
389
|
-
// Convert bytes::Bytes to Body
|
|
390
|
-
let response = response.body(Body::from(framed_payload)).map_err(|e| {
|
|
391
|
-
(
|
|
392
|
-
StatusCode::INTERNAL_SERVER_ERROR,
|
|
393
|
-
format!("Failed to build response: {}", e),
|
|
394
|
-
)
|
|
395
|
-
})?;
|
|
396
|
-
|
|
397
|
-
Ok(response)
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/// Handle a bidirectional streaming RPC request
|
|
401
|
-
///
|
|
402
|
-
/// Bidirectional streaming allows both client and server to send multiple messages.
|
|
403
|
-
/// This function:
|
|
404
|
-
/// 1. Keeps the request body as a stream (not converting to bytes)
|
|
405
|
-
/// 2. Copies HTTP headers to gRPC metadata
|
|
406
|
-
/// 3. Passes the streaming body to the service for frame parsing
|
|
407
|
-
/// 4. Returns the response stream from the handler
|
|
408
|
-
///
|
|
409
|
-
/// # Arguments
|
|
410
|
-
///
|
|
411
|
-
/// * `service` - The GenericGrpcService containing the handler
|
|
412
|
-
/// * `service_name` - Fully qualified service name (e.g., "mypackage.ChatService")
|
|
413
|
-
/// * `method_name` - Method name (e.g., "Chat")
|
|
414
|
-
/// * `max_message_size` - Maximum size per message in bytes
|
|
415
|
-
/// * `compression_enabled` - Whether compressed gRPC request frames are accepted
|
|
416
|
-
/// * `request` - Axum HTTP request with streaming body
|
|
417
|
-
///
|
|
418
|
-
/// # Returns
|
|
419
|
-
///
|
|
420
|
-
/// Response with streaming body containing response messages, or error with status code
|
|
421
|
-
async fn handle_bidirectional_streaming_request(
|
|
422
|
-
service: crate::grpc::GenericGrpcService,
|
|
423
|
-
service_name: String,
|
|
424
|
-
method_name: String,
|
|
425
|
-
max_message_size: usize,
|
|
426
|
-
compression_enabled: bool,
|
|
427
|
-
request: Request<Body>,
|
|
428
|
-
) -> Result<Response<Body>, (StatusCode, String)> {
|
|
429
|
-
// Extract request parts - keep body as stream for frame parsing
|
|
430
|
-
let (parts, body) = request.into_parts();
|
|
431
|
-
|
|
432
|
-
// Create a Tonic request with streaming body
|
|
433
|
-
// Body will be parsed by service.handle_bidi_stream using frame parser
|
|
434
|
-
let mut tonic_request = tonic::Request::new(body);
|
|
435
|
-
|
|
436
|
-
// Copy HTTP headers to gRPC metadata
|
|
437
|
-
for (key, value) in parts.headers.iter() {
|
|
438
|
-
if let Ok(value_str) = value.to_str()
|
|
439
|
-
&& let Ok(metadata_value) = value_str.parse::<tonic::metadata::MetadataValue<tonic::metadata::Ascii>>()
|
|
440
|
-
&& let Ok(metadata_key) = key
|
|
441
|
-
.as_str()
|
|
442
|
-
.parse::<tonic::metadata::MetadataKey<tonic::metadata::Ascii>>()
|
|
443
|
-
{
|
|
444
|
-
tonic_request.metadata_mut().insert(metadata_key, metadata_value);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Call service handler - frame parsing and size validation happens inside
|
|
449
|
-
let tonic_response = match service
|
|
450
|
-
.handle_bidi_stream(
|
|
451
|
-
service_name,
|
|
452
|
-
method_name,
|
|
453
|
-
tonic_request,
|
|
454
|
-
max_message_size,
|
|
455
|
-
compression_enabled,
|
|
456
|
-
)
|
|
457
|
-
.await
|
|
458
|
-
{
|
|
459
|
-
Ok(response) => response,
|
|
460
|
-
Err(status) => {
|
|
461
|
-
let status_code = grpc_status_to_http(status.code());
|
|
462
|
-
return Err((status_code, status.message().to_string()));
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
|
-
|
|
466
|
-
// Convert Tonic response to Axum response with streaming body
|
|
467
|
-
let body = tonic_response.into_inner();
|
|
468
|
-
let response = Response::builder()
|
|
469
|
-
.status(StatusCode::OK)
|
|
470
|
-
.header("content-type", "application/grpc+proto");
|
|
471
|
-
|
|
472
|
-
let response = response.body(body).map_err(|e| {
|
|
473
|
-
(
|
|
474
|
-
StatusCode::INTERNAL_SERVER_ERROR,
|
|
475
|
-
format!("Failed to build response: {}", e),
|
|
476
|
-
)
|
|
477
|
-
})?;
|
|
478
|
-
|
|
479
|
-
Ok(response)
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/// Check if an incoming request is a gRPC request
|
|
483
|
-
///
|
|
484
|
-
/// Returns true if the request has a content-type starting with "application/grpc"
|
|
485
|
-
pub fn is_grpc_request(request: &Request<Body>) -> bool {
|
|
486
|
-
request
|
|
487
|
-
.headers()
|
|
488
|
-
.get(axum::http::header::CONTENT_TYPE)
|
|
489
|
-
.and_then(|v| v.to_str().ok())
|
|
490
|
-
.map(|v| v.starts_with("application/grpc"))
|
|
491
|
-
.unwrap_or(false)
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
#[cfg(test)]
|
|
495
|
-
mod tests {
|
|
496
|
-
use super::*;
|
|
497
|
-
use crate::grpc::framing::{encode_grpc_message, parse_unary_grpc_message};
|
|
498
|
-
use crate::grpc::handler::{GrpcHandler, GrpcHandlerResult, GrpcRequestData, GrpcResponseData, RpcMode};
|
|
499
|
-
use crate::grpc::streaming::{MessageStream, StreamingRequest, message_stream_from_vec};
|
|
500
|
-
use bytes::Bytes;
|
|
501
|
-
use flate2::{Compression, write::GzEncoder};
|
|
502
|
-
use futures_util::StreamExt;
|
|
503
|
-
use http_body_util::BodyExt;
|
|
504
|
-
use std::future::Future;
|
|
505
|
-
use std::io::Write;
|
|
506
|
-
use std::pin::Pin;
|
|
507
|
-
use tonic::metadata::MetadataMap;
|
|
508
|
-
|
|
509
|
-
fn framed_message(payload: &[u8]) -> Bytes {
|
|
510
|
-
encode_grpc_message(Bytes::copy_from_slice(payload)).expect("test payload should frame")
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
struct EchoHandler;
|
|
514
|
-
|
|
515
|
-
impl GrpcHandler for EchoHandler {
|
|
516
|
-
fn call(&self, request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
517
|
-
Box::pin(async move {
|
|
518
|
-
Ok(GrpcResponseData {
|
|
519
|
-
payload: request.payload,
|
|
520
|
-
metadata: tonic::metadata::MetadataMap::new(),
|
|
521
|
-
})
|
|
522
|
-
})
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
fn service_name(&self) -> &str {
|
|
526
|
-
"test.EchoService"
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
#[tokio::test]
|
|
531
|
-
async fn test_route_grpc_request_success() {
|
|
532
|
-
let mut registry = GrpcRegistry::new();
|
|
533
|
-
registry.register_service("test.EchoService", Arc::new(EchoHandler), RpcMode::Unary);
|
|
534
|
-
let registry = Arc::new(registry);
|
|
535
|
-
let config = GrpcConfig::default();
|
|
536
|
-
|
|
537
|
-
let request = Request::builder()
|
|
538
|
-
.uri("/test.EchoService/Echo")
|
|
539
|
-
.header("content-type", "application/grpc")
|
|
540
|
-
.body(Body::from(framed_message(b"test payload")))
|
|
541
|
-
.unwrap();
|
|
542
|
-
|
|
543
|
-
let result = route_grpc_request(registry, &config, request).await;
|
|
544
|
-
assert!(result.is_ok());
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
#[tokio::test]
|
|
548
|
-
async fn test_route_grpc_request_supports_mixed_rpc_modes_per_service() {
|
|
549
|
-
struct MixedUnaryHandler;
|
|
550
|
-
|
|
551
|
-
impl GrpcHandler for MixedUnaryHandler {
|
|
552
|
-
fn call(&self, request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
553
|
-
Box::pin(async move {
|
|
554
|
-
Ok(GrpcResponseData {
|
|
555
|
-
payload: request.payload,
|
|
556
|
-
metadata: tonic::metadata::MetadataMap::new(),
|
|
557
|
-
})
|
|
558
|
-
})
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
fn service_name(&self) -> &str {
|
|
562
|
-
"test.MixedService"
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
struct MixedStreamHandler;
|
|
567
|
-
|
|
568
|
-
impl GrpcHandler for MixedStreamHandler {
|
|
569
|
-
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
570
|
-
Box::pin(async { Err(tonic::Status::unimplemented("Use server streaming")) })
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
fn service_name(&self) -> &str {
|
|
574
|
-
"test.MixedService"
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
fn call_server_stream(
|
|
578
|
-
&self,
|
|
579
|
-
_request: GrpcRequestData,
|
|
580
|
-
) -> Pin<Box<dyn Future<Output = Result<MessageStream, tonic::Status>> + Send>> {
|
|
581
|
-
Box::pin(async { Ok(message_stream_from_vec(vec![Bytes::from_static(b"streamed")])) })
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
let mut registry = GrpcRegistry::new();
|
|
586
|
-
registry.register(
|
|
587
|
-
"test.MixedService",
|
|
588
|
-
"GetUser",
|
|
589
|
-
Arc::new(MixedUnaryHandler),
|
|
590
|
-
RpcMode::Unary,
|
|
591
|
-
);
|
|
592
|
-
registry.register(
|
|
593
|
-
"test.MixedService",
|
|
594
|
-
"ListUsers",
|
|
595
|
-
Arc::new(MixedStreamHandler),
|
|
596
|
-
RpcMode::ServerStreaming,
|
|
597
|
-
);
|
|
598
|
-
let registry = Arc::new(registry);
|
|
599
|
-
let config = GrpcConfig::default();
|
|
600
|
-
|
|
601
|
-
let unary_response = route_grpc_request(
|
|
602
|
-
Arc::clone(®istry),
|
|
603
|
-
&config,
|
|
604
|
-
Request::builder()
|
|
605
|
-
.uri("/test.MixedService/GetUser")
|
|
606
|
-
.header("content-type", "application/grpc")
|
|
607
|
-
.body(Body::from(framed_message(b"user")))
|
|
608
|
-
.unwrap(),
|
|
609
|
-
)
|
|
610
|
-
.await
|
|
611
|
-
.unwrap();
|
|
612
|
-
assert_eq!(unary_response.headers().get("grpc-status").unwrap(), "0");
|
|
613
|
-
|
|
614
|
-
let streaming_response = route_grpc_request(
|
|
615
|
-
registry,
|
|
616
|
-
&config,
|
|
617
|
-
Request::builder()
|
|
618
|
-
.uri("/test.MixedService/ListUsers")
|
|
619
|
-
.header("content-type", "application/grpc")
|
|
620
|
-
.body(Body::from(framed_message(b"ignored")))
|
|
621
|
-
.unwrap(),
|
|
622
|
-
)
|
|
623
|
-
.await
|
|
624
|
-
.unwrap();
|
|
625
|
-
assert!(streaming_response.headers().get("grpc-status").is_none());
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
#[tokio::test]
|
|
629
|
-
async fn test_route_grpc_request_service_not_found() {
|
|
630
|
-
let registry = Arc::new(GrpcRegistry::new());
|
|
631
|
-
let config = GrpcConfig::default();
|
|
632
|
-
|
|
633
|
-
let request = Request::builder()
|
|
634
|
-
.uri("/nonexistent.Service/Method")
|
|
635
|
-
.header("content-type", "application/grpc")
|
|
636
|
-
.body(Body::from(Bytes::new()))
|
|
637
|
-
.unwrap();
|
|
638
|
-
|
|
639
|
-
let result = route_grpc_request(registry, &config, request).await;
|
|
640
|
-
assert!(result.is_err());
|
|
641
|
-
|
|
642
|
-
let (status, message) = result.unwrap_err();
|
|
643
|
-
assert_eq!(status, StatusCode::NOT_FOUND);
|
|
644
|
-
assert!(message.contains("Service not found"));
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
#[tokio::test]
|
|
648
|
-
async fn test_route_grpc_request_method_not_found() {
|
|
649
|
-
let mut registry = GrpcRegistry::new();
|
|
650
|
-
registry.register("test.EchoService", "Echo", Arc::new(EchoHandler), RpcMode::Unary);
|
|
651
|
-
let registry = Arc::new(registry);
|
|
652
|
-
let config = GrpcConfig::default();
|
|
653
|
-
|
|
654
|
-
let request = Request::builder()
|
|
655
|
-
.uri("/test.EchoService/Missing")
|
|
656
|
-
.header("content-type", "application/grpc")
|
|
657
|
-
.body(Body::from(Bytes::new()))
|
|
658
|
-
.unwrap();
|
|
659
|
-
|
|
660
|
-
let result = route_grpc_request(registry, &config, request).await;
|
|
661
|
-
assert!(result.is_err());
|
|
662
|
-
|
|
663
|
-
let (status, message) = result.unwrap_err();
|
|
664
|
-
assert_eq!(status, StatusCode::NOT_FOUND);
|
|
665
|
-
assert!(message.contains("Method not found"));
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
#[tokio::test]
|
|
669
|
-
async fn test_route_grpc_request_invalid_path() {
|
|
670
|
-
let registry = Arc::new(GrpcRegistry::new());
|
|
671
|
-
let config = GrpcConfig::default();
|
|
672
|
-
|
|
673
|
-
let request = Request::builder()
|
|
674
|
-
.uri("/invalid")
|
|
675
|
-
.header("content-type", "application/grpc")
|
|
676
|
-
.body(Body::from(Bytes::new()))
|
|
677
|
-
.unwrap();
|
|
678
|
-
|
|
679
|
-
let result = route_grpc_request(registry, &config, request).await;
|
|
680
|
-
assert!(result.is_err());
|
|
681
|
-
|
|
682
|
-
let (status, _message) = result.unwrap_err();
|
|
683
|
-
assert_eq!(status, StatusCode::BAD_REQUEST);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
#[test]
|
|
687
|
-
fn test_is_grpc_request_true() {
|
|
688
|
-
let request = Request::builder()
|
|
689
|
-
.header("content-type", "application/grpc")
|
|
690
|
-
.body(Body::empty())
|
|
691
|
-
.unwrap();
|
|
692
|
-
|
|
693
|
-
assert!(is_grpc_request(&request));
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
#[test]
|
|
697
|
-
fn test_is_grpc_request_with_subtype() {
|
|
698
|
-
let request = Request::builder()
|
|
699
|
-
.header("content-type", "application/grpc+proto")
|
|
700
|
-
.body(Body::empty())
|
|
701
|
-
.unwrap();
|
|
702
|
-
|
|
703
|
-
assert!(is_grpc_request(&request));
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
#[test]
|
|
707
|
-
fn test_is_grpc_request_false() {
|
|
708
|
-
let request = Request::builder()
|
|
709
|
-
.header("content-type", "application/json")
|
|
710
|
-
.body(Body::empty())
|
|
711
|
-
.unwrap();
|
|
712
|
-
|
|
713
|
-
assert!(!is_grpc_request(&request));
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
#[test]
|
|
717
|
-
fn test_is_grpc_request_no_content_type() {
|
|
718
|
-
let request = Request::builder().body(Body::empty()).unwrap();
|
|
719
|
-
|
|
720
|
-
assert!(!is_grpc_request(&request));
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
#[test]
|
|
724
|
-
fn test_grpc_status_to_http_mappings() {
|
|
725
|
-
// Test all gRPC status codes map correctly
|
|
726
|
-
assert_eq!(grpc_status_to_http(tonic::Code::Ok), StatusCode::OK);
|
|
727
|
-
assert_eq!(
|
|
728
|
-
grpc_status_to_http(tonic::Code::Cancelled),
|
|
729
|
-
StatusCode::from_u16(499).unwrap()
|
|
730
|
-
);
|
|
731
|
-
assert_eq!(
|
|
732
|
-
grpc_status_to_http(tonic::Code::Unknown),
|
|
733
|
-
StatusCode::INTERNAL_SERVER_ERROR
|
|
734
|
-
);
|
|
735
|
-
assert_eq!(
|
|
736
|
-
grpc_status_to_http(tonic::Code::InvalidArgument),
|
|
737
|
-
StatusCode::BAD_REQUEST
|
|
738
|
-
);
|
|
739
|
-
assert_eq!(
|
|
740
|
-
grpc_status_to_http(tonic::Code::DeadlineExceeded),
|
|
741
|
-
StatusCode::GATEWAY_TIMEOUT
|
|
742
|
-
);
|
|
743
|
-
assert_eq!(grpc_status_to_http(tonic::Code::NotFound), StatusCode::NOT_FOUND);
|
|
744
|
-
assert_eq!(grpc_status_to_http(tonic::Code::AlreadyExists), StatusCode::CONFLICT);
|
|
745
|
-
assert_eq!(
|
|
746
|
-
grpc_status_to_http(tonic::Code::PermissionDenied),
|
|
747
|
-
StatusCode::FORBIDDEN
|
|
748
|
-
);
|
|
749
|
-
assert_eq!(
|
|
750
|
-
grpc_status_to_http(tonic::Code::ResourceExhausted),
|
|
751
|
-
StatusCode::TOO_MANY_REQUESTS
|
|
752
|
-
);
|
|
753
|
-
assert_eq!(
|
|
754
|
-
grpc_status_to_http(tonic::Code::FailedPrecondition),
|
|
755
|
-
StatusCode::BAD_REQUEST
|
|
756
|
-
);
|
|
757
|
-
assert_eq!(grpc_status_to_http(tonic::Code::Aborted), StatusCode::CONFLICT);
|
|
758
|
-
assert_eq!(grpc_status_to_http(tonic::Code::OutOfRange), StatusCode::BAD_REQUEST);
|
|
759
|
-
assert_eq!(
|
|
760
|
-
grpc_status_to_http(tonic::Code::Unimplemented),
|
|
761
|
-
StatusCode::NOT_IMPLEMENTED
|
|
762
|
-
);
|
|
763
|
-
assert_eq!(
|
|
764
|
-
grpc_status_to_http(tonic::Code::Internal),
|
|
765
|
-
StatusCode::INTERNAL_SERVER_ERROR
|
|
766
|
-
);
|
|
767
|
-
assert_eq!(
|
|
768
|
-
grpc_status_to_http(tonic::Code::Unavailable),
|
|
769
|
-
StatusCode::SERVICE_UNAVAILABLE
|
|
770
|
-
);
|
|
771
|
-
assert_eq!(
|
|
772
|
-
grpc_status_to_http(tonic::Code::DataLoss),
|
|
773
|
-
StatusCode::INTERNAL_SERVER_ERROR
|
|
774
|
-
);
|
|
775
|
-
assert_eq!(
|
|
776
|
-
grpc_status_to_http(tonic::Code::Unauthenticated),
|
|
777
|
-
StatusCode::UNAUTHORIZED
|
|
778
|
-
);
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
#[tokio::test]
|
|
782
|
-
async fn test_route_grpc_request_with_custom_max_message_size() {
|
|
783
|
-
let mut registry = GrpcRegistry::new();
|
|
784
|
-
registry.register_service("test.EchoService", Arc::new(EchoHandler), RpcMode::Unary);
|
|
785
|
-
let registry = Arc::new(registry);
|
|
786
|
-
|
|
787
|
-
let mut config = GrpcConfig::default();
|
|
788
|
-
config.max_message_size = 100; // Set small limit
|
|
789
|
-
|
|
790
|
-
let request = Request::builder()
|
|
791
|
-
.uri("/test.EchoService/Echo")
|
|
792
|
-
.header("content-type", "application/grpc")
|
|
793
|
-
.body(Body::from(framed_message(b"test payload")))
|
|
794
|
-
.unwrap();
|
|
795
|
-
|
|
796
|
-
// This should succeed since payload is small
|
|
797
|
-
let result = route_grpc_request(registry.clone(), &config, request).await;
|
|
798
|
-
assert!(result.is_ok());
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
#[tokio::test]
|
|
802
|
-
async fn test_route_grpc_request_exceeds_max_message_size() {
|
|
803
|
-
let mut registry = GrpcRegistry::new();
|
|
804
|
-
registry.register_service("test.EchoService", Arc::new(EchoHandler), RpcMode::Unary);
|
|
805
|
-
let registry = Arc::new(registry);
|
|
806
|
-
|
|
807
|
-
let mut config = GrpcConfig::default();
|
|
808
|
-
config.max_message_size = 10; // Set very small limit
|
|
809
|
-
|
|
810
|
-
// Create a large payload that exceeds the limit
|
|
811
|
-
let large_payload = vec![b'x'; 1000];
|
|
812
|
-
let framed_payload = encode_grpc_message(Bytes::from(large_payload)).unwrap();
|
|
813
|
-
let request = Request::builder()
|
|
814
|
-
.uri("/test.EchoService/Echo")
|
|
815
|
-
.header("content-type", "application/grpc")
|
|
816
|
-
.body(Body::from(framed_payload))
|
|
817
|
-
.unwrap();
|
|
818
|
-
|
|
819
|
-
let result = route_grpc_request(registry, &config, request).await;
|
|
820
|
-
assert!(result.is_err());
|
|
821
|
-
|
|
822
|
-
let (status, message) = result.unwrap_err();
|
|
823
|
-
assert_eq!(status, StatusCode::PAYLOAD_TOO_LARGE);
|
|
824
|
-
assert!(message.contains("Message exceeds maximum size"));
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
#[tokio::test]
|
|
828
|
-
async fn test_route_grpc_request_server_streaming_success() {
|
|
829
|
-
struct StreamHandler;
|
|
830
|
-
|
|
831
|
-
impl GrpcHandler for StreamHandler {
|
|
832
|
-
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
833
|
-
Box::pin(async { Err(tonic::Status::unimplemented("Use server streaming")) })
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
fn service_name(&self) -> &str {
|
|
837
|
-
"test.StreamService"
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
fn call_server_stream(
|
|
841
|
-
&self,
|
|
842
|
-
_request: GrpcRequestData,
|
|
843
|
-
) -> Pin<Box<dyn Future<Output = Result<MessageStream, tonic::Status>> + Send>> {
|
|
844
|
-
Box::pin(async {
|
|
845
|
-
let messages = vec![Bytes::from("m1"), Bytes::from("m2")];
|
|
846
|
-
Ok(message_stream_from_vec(messages))
|
|
847
|
-
})
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
let mut registry = GrpcRegistry::new();
|
|
852
|
-
registry.register_service("test.StreamService", Arc::new(StreamHandler), RpcMode::ServerStreaming);
|
|
853
|
-
let registry = Arc::new(registry);
|
|
854
|
-
let config = GrpcConfig::default();
|
|
855
|
-
|
|
856
|
-
let request = Request::builder()
|
|
857
|
-
.uri("/test.StreamService/Stream")
|
|
858
|
-
.header("content-type", "application/grpc")
|
|
859
|
-
.body(Body::from(framed_message(b"ignored")))
|
|
860
|
-
.unwrap();
|
|
861
|
-
|
|
862
|
-
let response = route_grpc_request(registry, &config, request).await.unwrap();
|
|
863
|
-
assert_eq!(response.status(), StatusCode::OK);
|
|
864
|
-
assert_eq!(
|
|
865
|
-
response.headers().get("content-type").unwrap(),
|
|
866
|
-
"application/grpc+proto"
|
|
867
|
-
);
|
|
868
|
-
assert!(
|
|
869
|
-
response.headers().get("grpc-status").is_none(),
|
|
870
|
-
"server-streaming responses must not predeclare grpc-status"
|
|
871
|
-
);
|
|
872
|
-
|
|
873
|
-
let collected = response.into_body().collect().await.unwrap();
|
|
874
|
-
let trailers = collected.trailers().expect("grpc trailers");
|
|
875
|
-
let grpc_status = trailers.get("grpc-status").unwrap().to_str().unwrap().to_string();
|
|
876
|
-
let grpc_message = trailers.get("grpc-message").unwrap().to_str().unwrap().to_string();
|
|
877
|
-
let body = collected.to_bytes();
|
|
878
|
-
assert!(!body.is_empty());
|
|
879
|
-
assert_eq!(grpc_status, "0");
|
|
880
|
-
assert_eq!(grpc_message, "OK");
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
#[tokio::test]
|
|
884
|
-
async fn test_route_grpc_request_client_streaming_success_and_response_metadata() {
|
|
885
|
-
struct ClientStreamHandler;
|
|
886
|
-
|
|
887
|
-
impl GrpcHandler for ClientStreamHandler {
|
|
888
|
-
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
889
|
-
Box::pin(async { Err(tonic::Status::unimplemented("Use client streaming")) })
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
fn service_name(&self) -> &str {
|
|
893
|
-
"test.ClientStreamService"
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
fn call_client_stream(
|
|
897
|
-
&self,
|
|
898
|
-
mut request: StreamingRequest,
|
|
899
|
-
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, tonic::Status>> + Send>> {
|
|
900
|
-
Box::pin(async move {
|
|
901
|
-
// Make sure routing copied request headers into metadata.
|
|
902
|
-
assert!(request.metadata.get("x-request-id").is_some());
|
|
903
|
-
|
|
904
|
-
let mut first = None;
|
|
905
|
-
while let Some(item) = request.message_stream.next().await {
|
|
906
|
-
first = Some(item?);
|
|
907
|
-
break;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
let payload = first.unwrap_or_else(Bytes::new);
|
|
911
|
-
let mut metadata = MetadataMap::new();
|
|
912
|
-
metadata.insert("x-response-id", "resp-123".parse().unwrap());
|
|
913
|
-
|
|
914
|
-
Ok(GrpcResponseData { payload, metadata })
|
|
915
|
-
})
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
let mut registry = GrpcRegistry::new();
|
|
920
|
-
registry.register_service(
|
|
921
|
-
"test.ClientStreamService",
|
|
922
|
-
Arc::new(ClientStreamHandler),
|
|
923
|
-
RpcMode::ClientStreaming,
|
|
924
|
-
);
|
|
925
|
-
let registry = Arc::new(registry);
|
|
926
|
-
let config = GrpcConfig::default();
|
|
927
|
-
|
|
928
|
-
// Single gRPC frame: compression=0, length=5, message="hello"
|
|
929
|
-
let frame = vec![
|
|
930
|
-
0x00, // compression: no
|
|
931
|
-
0x00, 0x00, 0x00, 0x05, // length: 5 bytes
|
|
932
|
-
b'h', b'e', b'l', b'l', b'o',
|
|
933
|
-
];
|
|
934
|
-
|
|
935
|
-
let request = Request::builder()
|
|
936
|
-
.uri("/test.ClientStreamService/ClientStream")
|
|
937
|
-
.header("content-type", "application/grpc")
|
|
938
|
-
.header("x-request-id", "req-123")
|
|
939
|
-
.body(Body::from(frame))
|
|
940
|
-
.unwrap();
|
|
941
|
-
|
|
942
|
-
let response = route_grpc_request(registry, &config, request).await.unwrap();
|
|
943
|
-
assert_eq!(response.status(), StatusCode::OK);
|
|
944
|
-
assert_eq!(response.headers().get("grpc-status").unwrap(), "0");
|
|
945
|
-
assert_eq!(response.headers().get("x-response-id").unwrap(), "resp-123");
|
|
946
|
-
|
|
947
|
-
let body = axum::body::to_bytes(response.into_body(), 1024).await.unwrap();
|
|
948
|
-
let payload = parse_unary_grpc_message(&body, 1024, None, true).unwrap();
|
|
949
|
-
assert_eq!(payload, Bytes::from_static(b"hello"));
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
#[tokio::test]
|
|
953
|
-
async fn test_route_grpc_request_bidi_streaming_success() {
|
|
954
|
-
struct BidiHandler;
|
|
955
|
-
|
|
956
|
-
impl GrpcHandler for BidiHandler {
|
|
957
|
-
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
958
|
-
Box::pin(async { Err(tonic::Status::unimplemented("Use bidi streaming")) })
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
fn service_name(&self) -> &str {
|
|
962
|
-
"test.BidiService"
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
fn call_bidi_stream(
|
|
966
|
-
&self,
|
|
967
|
-
_request: StreamingRequest,
|
|
968
|
-
) -> Pin<Box<dyn Future<Output = Result<MessageStream, tonic::Status>> + Send>> {
|
|
969
|
-
Box::pin(async {
|
|
970
|
-
let messages = vec![Bytes::from("r1")];
|
|
971
|
-
Ok(message_stream_from_vec(messages))
|
|
972
|
-
})
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
let mut registry = GrpcRegistry::new();
|
|
977
|
-
registry.register_service(
|
|
978
|
-
"test.BidiService",
|
|
979
|
-
Arc::new(BidiHandler),
|
|
980
|
-
RpcMode::BidirectionalStreaming,
|
|
981
|
-
);
|
|
982
|
-
let registry = Arc::new(registry);
|
|
983
|
-
let config = GrpcConfig::default();
|
|
984
|
-
|
|
985
|
-
// Provide a well-formed request frame so the frame parser succeeds.
|
|
986
|
-
let frame = vec![
|
|
987
|
-
0x00, // compression: no
|
|
988
|
-
0x00, 0x00, 0x00, 0x01, // length: 1 byte
|
|
989
|
-
b'x',
|
|
990
|
-
];
|
|
991
|
-
|
|
992
|
-
let request = Request::builder()
|
|
993
|
-
.uri("/test.BidiService/Chat")
|
|
994
|
-
.header("content-type", "application/grpc")
|
|
995
|
-
.body(Body::from(frame))
|
|
996
|
-
.unwrap();
|
|
997
|
-
|
|
998
|
-
let response = route_grpc_request(registry, &config, request).await.unwrap();
|
|
999
|
-
assert_eq!(response.status(), StatusCode::OK);
|
|
1000
|
-
assert!(
|
|
1001
|
-
response.headers().get("grpc-status").is_none(),
|
|
1002
|
-
"bidi-streaming responses must not predeclare grpc-status"
|
|
1003
|
-
);
|
|
1004
|
-
|
|
1005
|
-
let collected = response.into_body().collect().await.unwrap();
|
|
1006
|
-
let trailers = collected.trailers().expect("grpc trailers");
|
|
1007
|
-
let grpc_status = trailers.get("grpc-status").unwrap().to_str().unwrap().to_string();
|
|
1008
|
-
let grpc_message = trailers.get("grpc-message").unwrap().to_str().unwrap().to_string();
|
|
1009
|
-
let body = collected.to_bytes();
|
|
1010
|
-
assert!(!body.is_empty());
|
|
1011
|
-
assert_eq!(grpc_status, "0");
|
|
1012
|
-
assert_eq!(grpc_message, "OK");
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
#[tokio::test]
|
|
1016
|
-
async fn test_route_grpc_request_client_streaming_unsupported_encoding_maps_to_501() {
|
|
1017
|
-
// Use a handler that would succeed, but the request is invalid before handler gets called.
|
|
1018
|
-
struct NeverCalledHandler;
|
|
1019
|
-
|
|
1020
|
-
impl GrpcHandler for NeverCalledHandler {
|
|
1021
|
-
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
1022
|
-
Box::pin(async { Err(tonic::Status::unimplemented("not used")) })
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
fn service_name(&self) -> &str {
|
|
1026
|
-
"test.BadClientStreamService"
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
fn call_client_stream(
|
|
1030
|
-
&self,
|
|
1031
|
-
_request: StreamingRequest,
|
|
1032
|
-
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, tonic::Status>> + Send>> {
|
|
1033
|
-
Box::pin(async {
|
|
1034
|
-
Ok(GrpcResponseData {
|
|
1035
|
-
payload: Bytes::from_static(b"ok"),
|
|
1036
|
-
metadata: MetadataMap::new(),
|
|
1037
|
-
})
|
|
1038
|
-
})
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
let mut registry = GrpcRegistry::new();
|
|
1043
|
-
registry.register_service(
|
|
1044
|
-
"test.BadClientStreamService",
|
|
1045
|
-
Arc::new(NeverCalledHandler),
|
|
1046
|
-
RpcMode::ClientStreaming,
|
|
1047
|
-
);
|
|
1048
|
-
let registry = Arc::new(registry);
|
|
1049
|
-
let config = GrpcConfig::default();
|
|
1050
|
-
|
|
1051
|
-
// Compression flag = 1 with unsupported grpc-encoding => UNIMPLEMENTED.
|
|
1052
|
-
let frame = vec![
|
|
1053
|
-
0x01, // compression: yes
|
|
1054
|
-
0x00, 0x00, 0x00, 0x01, // length: 1 byte
|
|
1055
|
-
b'x',
|
|
1056
|
-
];
|
|
1057
|
-
|
|
1058
|
-
let request = Request::builder()
|
|
1059
|
-
.uri("/test.BadClientStreamService/ClientStream")
|
|
1060
|
-
.header("content-type", "application/grpc")
|
|
1061
|
-
.header("grpc-encoding", "br")
|
|
1062
|
-
.body(Body::from(frame))
|
|
1063
|
-
.unwrap();
|
|
1064
|
-
|
|
1065
|
-
let err = route_grpc_request(registry, &config, request).await.unwrap_err();
|
|
1066
|
-
assert_eq!(err.0, StatusCode::NOT_IMPLEMENTED);
|
|
1067
|
-
assert!(err.1.contains("Unsupported grpc-encoding"));
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
#[tokio::test]
|
|
1071
|
-
async fn test_route_grpc_request_client_streaming_missing_encoding_header_maps_to_400() {
|
|
1072
|
-
struct NeverCalledHandler;
|
|
1073
|
-
|
|
1074
|
-
impl GrpcHandler for NeverCalledHandler {
|
|
1075
|
-
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
1076
|
-
Box::pin(async { Err(tonic::Status::unimplemented("not used")) })
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
fn service_name(&self) -> &str {
|
|
1080
|
-
"test.BadClientStreamService"
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
fn call_client_stream(
|
|
1084
|
-
&self,
|
|
1085
|
-
_request: StreamingRequest,
|
|
1086
|
-
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, tonic::Status>> + Send>> {
|
|
1087
|
-
Box::pin(async {
|
|
1088
|
-
Ok(GrpcResponseData {
|
|
1089
|
-
payload: Bytes::from_static(b"ok"),
|
|
1090
|
-
metadata: MetadataMap::new(),
|
|
1091
|
-
})
|
|
1092
|
-
})
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
let mut registry = GrpcRegistry::new();
|
|
1097
|
-
registry.register_service(
|
|
1098
|
-
"test.BadClientStreamService",
|
|
1099
|
-
Arc::new(NeverCalledHandler),
|
|
1100
|
-
RpcMode::ClientStreaming,
|
|
1101
|
-
);
|
|
1102
|
-
let registry = Arc::new(registry);
|
|
1103
|
-
let config = GrpcConfig::default();
|
|
1104
|
-
|
|
1105
|
-
// Compression flag = 1 with no grpc-encoding => INVALID_ARGUMENT.
|
|
1106
|
-
let frame = vec![
|
|
1107
|
-
0x01, // compression: yes
|
|
1108
|
-
0x00, 0x00, 0x00, 0x01, // length: 1 byte
|
|
1109
|
-
b'x',
|
|
1110
|
-
];
|
|
1111
|
-
|
|
1112
|
-
let request = Request::builder()
|
|
1113
|
-
.uri("/test.BadClientStreamService/ClientStream")
|
|
1114
|
-
.header("content-type", "application/grpc")
|
|
1115
|
-
.body(Body::from(frame))
|
|
1116
|
-
.unwrap();
|
|
1117
|
-
|
|
1118
|
-
let err = route_grpc_request(registry, &config, request).await.unwrap_err();
|
|
1119
|
-
assert_eq!(err.0, StatusCode::BAD_REQUEST);
|
|
1120
|
-
assert!(err.1.contains("missing grpc-encoding"));
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
#[tokio::test]
|
|
1124
|
-
async fn test_route_grpc_request_client_streaming_gzip_success() {
|
|
1125
|
-
struct ClientStreamHandler;
|
|
1126
|
-
|
|
1127
|
-
impl GrpcHandler for ClientStreamHandler {
|
|
1128
|
-
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
1129
|
-
Box::pin(async { Err(tonic::Status::unimplemented("Use client streaming")) })
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
fn service_name(&self) -> &str {
|
|
1133
|
-
"test.CompressedClientStreamService"
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
fn call_client_stream(
|
|
1137
|
-
&self,
|
|
1138
|
-
mut request: StreamingRequest,
|
|
1139
|
-
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, tonic::Status>> + Send>> {
|
|
1140
|
-
Box::pin(async move {
|
|
1141
|
-
let payload = request
|
|
1142
|
-
.message_stream
|
|
1143
|
-
.next()
|
|
1144
|
-
.await
|
|
1145
|
-
.transpose()?
|
|
1146
|
-
.unwrap_or_else(Bytes::new);
|
|
1147
|
-
Ok(GrpcResponseData {
|
|
1148
|
-
payload,
|
|
1149
|
-
metadata: MetadataMap::new(),
|
|
1150
|
-
})
|
|
1151
|
-
})
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
let mut registry = GrpcRegistry::new();
|
|
1156
|
-
registry.register_service(
|
|
1157
|
-
"test.CompressedClientStreamService",
|
|
1158
|
-
Arc::new(ClientStreamHandler),
|
|
1159
|
-
RpcMode::ClientStreaming,
|
|
1160
|
-
);
|
|
1161
|
-
let registry = Arc::new(registry);
|
|
1162
|
-
let config = GrpcConfig::default();
|
|
1163
|
-
|
|
1164
|
-
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
|
|
1165
|
-
encoder.write_all(b"hello").unwrap();
|
|
1166
|
-
let compressed_payload = encoder.finish().unwrap();
|
|
1167
|
-
|
|
1168
|
-
let mut frame = Vec::new();
|
|
1169
|
-
frame.push(0x01); // compression: yes
|
|
1170
|
-
frame.extend_from_slice(&(compressed_payload.len() as u32).to_be_bytes());
|
|
1171
|
-
frame.extend_from_slice(&compressed_payload);
|
|
1172
|
-
|
|
1173
|
-
let request = Request::builder()
|
|
1174
|
-
.uri("/test.CompressedClientStreamService/ClientStream")
|
|
1175
|
-
.header("content-type", "application/grpc")
|
|
1176
|
-
.header("grpc-encoding", "gzip")
|
|
1177
|
-
.body(Body::from(frame))
|
|
1178
|
-
.unwrap();
|
|
1179
|
-
|
|
1180
|
-
let response = route_grpc_request(registry, &config, request).await.unwrap();
|
|
1181
|
-
assert_eq!(response.status(), StatusCode::OK);
|
|
1182
|
-
let body = axum::body::to_bytes(response.into_body(), 1024).await.unwrap();
|
|
1183
|
-
let payload = parse_unary_grpc_message(&body, 1024, None, true).unwrap();
|
|
1184
|
-
assert_eq!(payload, Bytes::from_static(b"hello"));
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
#[tokio::test]
|
|
1188
|
-
async fn test_route_grpc_request_client_streaming_gzip_when_compression_disabled_maps_to_501() {
|
|
1189
|
-
struct NeverCalledHandler;
|
|
1190
|
-
|
|
1191
|
-
impl GrpcHandler for NeverCalledHandler {
|
|
1192
|
-
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
1193
|
-
Box::pin(async { Err(tonic::Status::unimplemented("not used")) })
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
fn service_name(&self) -> &str {
|
|
1197
|
-
"test.DisabledCompressionService"
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
fn call_client_stream(
|
|
1201
|
-
&self,
|
|
1202
|
-
_request: StreamingRequest,
|
|
1203
|
-
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, tonic::Status>> + Send>> {
|
|
1204
|
-
Box::pin(async {
|
|
1205
|
-
Ok(GrpcResponseData {
|
|
1206
|
-
payload: Bytes::from_static(b"ok"),
|
|
1207
|
-
metadata: MetadataMap::new(),
|
|
1208
|
-
})
|
|
1209
|
-
})
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
let mut registry = GrpcRegistry::new();
|
|
1214
|
-
registry.register_service(
|
|
1215
|
-
"test.DisabledCompressionService",
|
|
1216
|
-
Arc::new(NeverCalledHandler),
|
|
1217
|
-
RpcMode::ClientStreaming,
|
|
1218
|
-
);
|
|
1219
|
-
let registry = Arc::new(registry);
|
|
1220
|
-
let mut config = GrpcConfig::default();
|
|
1221
|
-
config.enable_compression = false;
|
|
1222
|
-
|
|
1223
|
-
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
|
|
1224
|
-
encoder.write_all(b"hello").unwrap();
|
|
1225
|
-
let compressed_payload = encoder.finish().unwrap();
|
|
1226
|
-
|
|
1227
|
-
let mut frame = Vec::new();
|
|
1228
|
-
frame.push(0x01); // compression: yes
|
|
1229
|
-
frame.extend_from_slice(&(compressed_payload.len() as u32).to_be_bytes());
|
|
1230
|
-
frame.extend_from_slice(&compressed_payload);
|
|
1231
|
-
|
|
1232
|
-
let request = Request::builder()
|
|
1233
|
-
.uri("/test.DisabledCompressionService/ClientStream")
|
|
1234
|
-
.header("content-type", "application/grpc")
|
|
1235
|
-
.header("grpc-encoding", "gzip")
|
|
1236
|
-
.body(Body::from(frame))
|
|
1237
|
-
.unwrap();
|
|
1238
|
-
|
|
1239
|
-
let err = route_grpc_request(registry, &config, request).await.unwrap_err();
|
|
1240
|
-
assert_eq!(err.0, StatusCode::NOT_IMPLEMENTED);
|
|
1241
|
-
assert!(err.1.contains("disabled"));
|
|
1242
|
-
}
|
|
1243
|
-
}
|