Skip to main content

ri/c/
gateway.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Gateway Module C API
19//!
20//! This module provides C language bindings for Ri's API gateway subsystem. The gateway module
21//! delivers high-performance HTTP request routing, load balancing, rate limiting, and request/response
22//! transformation capabilities. This C API enables C/C++ applications to leverage Ri's gateway
23//! functionality for building scalable API endpoints with enterprise-grade features.
24//!
25//! ## Module Architecture
26//!
27//! The gateway module comprises three primary components that together provide complete API gateway
28//! functionality:
29//!
30//! - **RiGateway**: Core gateway server implementation handling HTTP request processing,
31//!   middleware composition, and response generation. The gateway acts as the entry point for
32//!   all incoming API requests, applying configured middleware chains and routing requests to
33//!   appropriate backend services.
34//!
35//! - **RiGatewayConfig**: Configuration container for gateway server parameters including listen
36//!   address, thread pool sizing, TLS settings, and middleware configuration. The configuration
37//!   object controls resource allocation, security settings, and behavioral characteristics.
38//!
39//! - **RiRouter**: Request routing component responsible for matching incoming requests to
40//!   registered routes based on method, path, headers, and other request attributes. The router
41//!   supports complex routing patterns including path parameters, wildcards, and regex matching.
42//!
43//! ## Gateway Features
44//!
45//! The API gateway provides comprehensive features for production API management:
46//!
47//! - **Request Routing**: Advanced routing capabilities including path matching, method-based
48//!   routing, header-based routing, and query parameter routing. Supports route groups and
49//!   hierarchical routing patterns.
50//!
51//! - **Load Balancing**: Distribution of requests across multiple backend instances with
52//!   configurable algorithms including round-robin, least-connections, weighted distribution,
53//!   and consistent hashing for session affinity.
54//!
55//! - **Rate Limiting**: Request rate control at multiple granularity levels including global,
56//!   per-client, per-route, and per-user rate limits. Supports sliding window, token bucket,
57//!   and leaky bucket algorithms.
58//!
59//! - **Middleware Chain**: Composable middleware for request/response processing including
60//!   authentication, authorization, logging, compression, caching, and transformation.
61//!   Middleware executes in configured order with early exit capabilities.
62//!
63//! - **Request/Response Transformation**: Content transformation between client and backend
64//!   formats including JSON to XML conversion, header manipulation, body rewriting, and
65//!   protocol translation.
66//!
67//! - **Circuit Breaker**: Automatic detection of backend failures with configurable thresholds
68//!   for failure rates, timeout windows, and recovery strategies. Prevents cascade failures
69//!   in distributed systems.
70//!
71//! - **WebSocket Support**: Full-duplex WebSocket connections with session management,
72//!   heartbeat handling, and connection lifecycle events.
73//!
74//! - **TLS Termination**: Secure communication with configurable TLS settings including
75//!   certificate management, cipher suite selection, and HTTP/2 support.
76//!
77//! ## Routing Capabilities
78//!
79//! The router supports sophisticated routing patterns:
80//!
81//! - **Static Paths**: Exact path matching for simple routes (e.g., /api/users)
82//!
83//! - **Path Parameters**: Variable path segments captured as parameters (e.g., /users/:id)
84//!
85//! - **Wildcard Matching**: Catch-all routes for static file serving (e.g., /static/*)
86//!
87//! - **Regex Patterns**: Complex matching rules using regular expressions
88//!
89//! - **Method Matching**: Route requests by HTTP method (GET, POST, PUT, DELETE, etc.)
90//!
91//! - **Header Matching**: Route based on request headers (e.g., X-API-Version)
92//!
93//! - **Query Matching**: Route based on query parameters
94//!
95//! ## Middleware Types
96//!
97//! The gateway supports various middleware categories:
98//!
99//! - **Authentication Middleware**: JWT validation, OAuth token verification, API key checking,
100//!   and custom authentication schemes.
101//!
102//! - **Authorization Middleware**: Role-based access control, permission checking, and
103//!   policy enforcement at route level.
104//!
105//! - **Logging Middleware**: Request/response logging, access logging, and structured logging
106//!   for observability.
107//!
108//! - **Compression Middleware**: Gzip, Brotli, and Deflate compression for response bodies.
109//!
110//! - **Caching Middleware**: Response caching with TTL control, cache invalidation, and
111//!   conditional requests (ETag, If-Modified-Since).
112//!
113//! - **Transformation Middleware**: Header addition/removal, body transformation, and content
114//!   type conversion.
115//!
116//! - **Rate Limiting Middleware**: Request throttling with configurable limits and response
117//!   handling.
118//!
119//! - **Circuit Breaker Middleware**: Failure detection and fallback handling for backend
120//!   services.
121//!
122//! ## Load Balancing Strategies
123//!
124//! The gateway implements multiple load balancing algorithms:
125//!
126//! - **Round Robin**: Sequential distribution across available backends. Simple and effective
127//!   for homogeneous backends with similar capacity.
128//!
129//! - **Least Connections**: Route to backend with fewest active connections. Adapts to
130//!   varying request processing times.
131//!
132//! - **Weighted Distribution**: Proportional routing based on backend capacity or priority.
133//!   Requires backend health and capacity configuration.
134//!
135//! - **Consistent Hashing**: Request affinity based on request attributes (e.g., user ID).
136//!   Minimizes redistribution when backends change.
137//!
138//! - **Random**: Random selection among healthy backends. Simple and effective for
139//!   homogeneous backends.
140//!
141//! ## Memory Management
142//!
143//! All C API objects use opaque pointers with manual memory management:
144//!
145//! - Constructor functions allocate new instances on the heap
146//! - Destructor functions must be called to release memory
147//! - Route handlers must be properly registered and unregistered
148//! - Connection handles must be properly closed
149//!
150//! ## Thread Safety
151//!
152//! The underlying implementations are thread-safe:
153//!
154//! - Gateway server handles concurrent requests using thread pool
155//! - Router operations are thread-safe for route lookups
156//! - Configuration changes may require gateway restart
157//! - Middleware should be stateless when possible
158//!
159//! ## Performance Characteristics
160//!
161//! Gateway operations have the following performance profiles:
162//!
163//! - Request routing: O(log n) for route lookup with path parameters
164//! - Middleware processing: O(m) where m is number of middleware
165//! - Load balancing: O(1) for most algorithms
166//! - Request throughput: Thousands of requests per second per core
167//!
168//! ## Usage Example
169//!
170//! ```c
171//! // Create gateway configuration
172//! RiGatewayConfig* config = ri_gateway_config_new();
173//! ri_gateway_config_set_address(config, "0.0.0.0", 8080);
174//! ri_gateway_config_set_workers(config, 4);
175//! ri_gateway_config_set_tls_enabled(config, false);
176//!
177//! // Create gateway instance
178//! RiGateway* gateway = ri_gateway_new(config);
179//!
180//! // Create router and configure routes
181//! RiRouter* router = ri_router_new();
182//!
183//! // Register routes
184//! ri_router_add_route(router, "GET", "/api/users", handle_users);
185//! ri_router_add_route(router, "POST", "/api/users", create_user);
186//! ri_router_add_route(router, "GET", "/api/users/:id", get_user_by_id);
187//!
188//! // Mount router on gateway
189//! ri_gateway_mount(gateway, "/api", router);
190//!
191//! // Start gateway
192//! ri_gateway_start(gateway);
193//!
194//! // Graceful shutdown on signal
195//! // ri_gateway_shutdown(gateway);
196//!
197//! // Cleanup
198//! ri_gateway_free(gateway);
199//! ri_gateway_config_free(config);
200//! ```
201//!
202//! ## Dependencies
203//!
204//! This module depends on the following Ri components:
205//!
206//! - `crate::gateway`: Rust gateway module implementation
207//! - `crate::prelude`: Common types and traits
208//! - Hyper for HTTP server functionality
209//! - Redis for rate limiting and session storage
210//!
211//! ## Feature Flags
212//!
213//! The gateway module is enabled by default with the "gateway" feature flag.
214//! Disable this feature to reduce binary size when gateway functionality is not required.
215
216use crate::gateway::{
217    RiGateway, RiGatewayConfig, RiRouter, RiRoute, RiRouteHandler,
218    RiRateLimiter, RiRateLimitConfig,
219    RiCircuitBreaker, RiCircuitBreakerConfig, RiCircuitBreakerState,
220    RiLoadBalancer, RiLoadBalancerStrategy, RiBackendServer,
221    RiGatewayRequest, RiGatewayResponse,
222};
223use crate::core::RiResult;
224use std::ffi::{c_char, c_int};
225use std::future::Future;
226use std::pin::Pin;
227use std::sync::Arc;
228
229c_wrapper!(CRiGateway, RiGateway);
230c_wrapper!(CRiGatewayConfig, RiGatewayConfig);
231c_wrapper!(CRiRouter, RiRouter);
232c_wrapper!(CRiRateLimiter, RiRateLimiter);
233c_wrapper!(CRiRateLimitConfig, RiRateLimitConfig);
234c_wrapper!(CRiCircuitBreaker, RiCircuitBreaker);
235c_wrapper!(CRiCircuitBreakerConfig, RiCircuitBreakerConfig);
236c_wrapper!(CRiLoadBalancer, RiLoadBalancer);
237
238c_constructor!(ri_gateway_config_new, CRiGatewayConfig, RiGatewayConfig, RiGatewayConfig::default());
239c_destructor!(ri_gateway_config_free, CRiGatewayConfig);
240
241#[no_mangle]
242pub extern "C" fn ri_router_new() -> *mut CRiRouter {
243    let router = RiRouter::new();
244    let ptr = Box::into_raw(Box::new(CRiRouter::new(router)));
245    crate::c::register_ptr(ptr as usize);
246    ptr
247}
248
249#[no_mangle]
250pub extern "C" fn ri_router_free(router: *mut CRiRouter) {
251    if router.is_null() {
252        return;
253    }
254
255    if !crate::c::unregister_ptr(router as usize) {
256        log::warn!(
257            "[Ri.C] Attempted to free unregistered or already freed router: {:?}",
258            router
259        );
260        return;
261    }
262
263    unsafe {
264        let _ = Box::from_raw(router);
265    }
266}
267
268#[no_mangle]
269pub extern "C" fn ri_router_add_route(
270    router: *mut CRiRouter,
271    method: *const c_char,
272    path: *const c_char,
273) -> c_int {
274    if router.is_null() || method.is_null() || path.is_null() {
275        return -1;
276    }
277
278    unsafe {
279        let method_str = match std::ffi::CStr::from_ptr(method).to_str() {
280            Ok(s) => s,
281            Err(_) => return -2,
282        };
283
284        let path_str = match std::ffi::CStr::from_ptr(path).to_str() {
285            Ok(s) => s,
286            Err(_) => return -3,
287        };
288
289        let handler: RiRouteHandler = Arc::new(|_req: RiGatewayRequest| {
290            Box::pin(async move {
291                Ok(crate::gateway::RiGatewayResponse::new(
292                    200,
293                    b"OK".to_vec(),
294                    String::new(),
295                ))
296            }) as Pin<Box<dyn Future<Output = RiResult<RiGatewayResponse>> + Send>>
297        });
298
299        let route = RiRoute::new(method_str.to_string(), path_str.to_string(), handler);
300        (*router).inner.add_route(route);
301        0
302    }
303}
304
305#[no_mangle]
306pub extern "C" fn ri_router_clear_routes(router: *mut CRiRouter) {
307    if router.is_null() {
308        return;
309    }
310    unsafe {
311        (*router).inner.clear_routes();
312    }
313}
314
315#[no_mangle]
316pub extern "C" fn ri_router_route_count(router: *mut CRiRouter) -> usize {
317    if router.is_null() {
318        return 0;
319    }
320    unsafe {
321        (*router).inner.route_count()
322    }
323}
324
325#[no_mangle]
326pub extern "C" fn ri_rate_limiter_new(
327    requests_per_second: u32,
328    burst_size: u32,
329    window_seconds: u64,
330) -> *mut CRiRateLimiter {
331    let config = RiRateLimitConfig {
332        requests_per_second,
333        burst_size,
334        window_seconds,
335        max_keys: 10000,
336    };
337    let limiter = RiRateLimiter::new(config);
338    Box::into_raw(Box::new(CRiRateLimiter::new(limiter)))
339}
340
341#[no_mangle]
342pub extern "C" fn ri_rate_limiter_free(limiter: *mut CRiRateLimiter) {
343    if !limiter.is_null() {
344        unsafe {
345            let _ = Box::from_raw(limiter);
346        }
347    }
348}
349
350#[no_mangle]
351pub extern "C" fn ri_rate_limiter_check(
352    limiter: *mut CRiRateLimiter,
353    key: *const c_char,
354) -> c_int {
355    if limiter.is_null() || key.is_null() {
356        return -1;
357    }
358
359    unsafe {
360        let key_str = match std::ffi::CStr::from_ptr(key).to_str() {
361            Ok(s) => s,
362            Err(_) => return -2,
363        };
364
365        if (*limiter).inner.check_rate_limit(key_str, 1) {
366            0
367        } else {
368            1
369        }
370    }
371}
372
373#[no_mangle]
374pub extern "C" fn ri_rate_limiter_get_remaining(
375    limiter: *mut CRiRateLimiter,
376    key: *const c_char,
377) -> f64 {
378    if limiter.is_null() || key.is_null() {
379        return -1.0;
380    }
381
382    unsafe {
383        let key_str = match std::ffi::CStr::from_ptr(key).to_str() {
384            Ok(s) => s,
385            Err(_) => return -2.0,
386        };
387
388        (*limiter).inner.get_remaining(key_str).unwrap_or(0.0)
389    }
390}
391
392#[no_mangle]
393pub extern "C" fn ri_rate_limiter_reset_bucket(
394    limiter: *mut CRiRateLimiter,
395    key: *const c_char,
396) {
397    if limiter.is_null() || key.is_null() {
398        return;
399    }
400
401    unsafe {
402        let key_str = match std::ffi::CStr::from_ptr(key).to_str() {
403            Ok(s) => s,
404            Err(_) => return,
405        };
406
407        (*limiter).inner.reset_bucket(key_str);
408    }
409}
410
411#[no_mangle]
412pub extern "C" fn ri_rate_limiter_clear_all(limiter: *mut CRiRateLimiter) {
413    if limiter.is_null() {
414        return;
415    }
416    unsafe {
417        (*limiter).inner.clear_all_buckets();
418    }
419}
420
421#[repr(C)]
422pub struct CRiRateLimitStats {
423    pub current_tokens: usize,
424    pub total_requests: usize,
425}
426
427#[no_mangle]
428pub extern "C" fn ri_rate_limiter_get_stats(
429    limiter: *mut CRiRateLimiter,
430    key: *const c_char,
431    out_stats: *mut CRiRateLimitStats,
432) -> c_int {
433    if limiter.is_null() || key.is_null() || out_stats.is_null() {
434        return -1;
435    }
436
437    unsafe {
438        let key_str = match std::ffi::CStr::from_ptr(key).to_str() {
439            Ok(s) => s,
440            Err(_) => return -2,
441        };
442
443        if let Some(stats) = (*limiter).inner.get_stats(key_str) {
444            *out_stats = CRiRateLimitStats {
445                current_tokens: stats.current_tokens,
446                total_requests: stats.total_requests,
447            };
448            0
449        } else {
450            -3
451        }
452    }
453}
454
455#[no_mangle]
456pub extern "C" fn ri_circuit_breaker_new(
457    failure_threshold: u32,
458    success_threshold: u32,
459    timeout_seconds: u64,
460) -> *mut CRiCircuitBreaker {
461    let config = RiCircuitBreakerConfig {
462        failure_threshold,
463        success_threshold,
464        timeout_seconds,
465        monitoring_period_seconds: 30,
466    };
467    let cb = RiCircuitBreaker::new(config);
468    Box::into_raw(Box::new(CRiCircuitBreaker::new(cb)))
469}
470
471#[no_mangle]
472pub extern "C" fn ri_circuit_breaker_free(cb: *mut CRiCircuitBreaker) {
473    if !cb.is_null() {
474        unsafe {
475            let _ = Box::from_raw(cb);
476        }
477    }
478}
479
480#[no_mangle]
481pub extern "C" fn ri_circuit_breaker_allow_request(cb: *mut CRiCircuitBreaker) -> c_int {
482    if cb.is_null() {
483        return 0;
484    }
485    unsafe {
486        if (*cb).inner.allow_request() { 1 } else { 0 }
487    }
488}
489
490#[no_mangle]
491pub extern "C" fn ri_circuit_breaker_record_success(cb: *mut CRiCircuitBreaker) {
492    if cb.is_null() {
493        return;
494    }
495    unsafe {
496        (*cb).inner.record_success();
497    }
498}
499
500#[no_mangle]
501pub extern "C" fn ri_circuit_breaker_record_failure(cb: *mut CRiCircuitBreaker) {
502    if cb.is_null() {
503        return;
504    }
505    unsafe {
506        (*cb).inner.record_failure();
507    }
508}
509
510pub const RI_CB_STATE_CLOSED: c_int = 0;
511pub const RI_CB_STATE_OPEN: c_int = 1;
512pub const RI_CB_STATE_HALF_OPEN: c_int = 2;
513
514#[no_mangle]
515pub extern "C" fn ri_circuit_breaker_get_state(cb: *mut CRiCircuitBreaker) -> c_int {
516    if cb.is_null() {
517        return RI_CB_STATE_CLOSED;
518    }
519    unsafe {
520        match (*cb).inner.get_state() {
521            RiCircuitBreakerState::Closed => RI_CB_STATE_CLOSED,
522            RiCircuitBreakerState::Open => RI_CB_STATE_OPEN,
523            RiCircuitBreakerState::HalfOpen => RI_CB_STATE_HALF_OPEN,
524        }
525    }
526}
527
528#[repr(C)]
529pub struct CRiCircuitBreakerMetrics {
530    pub state: c_int,
531    pub failure_count: usize,
532    pub success_count: usize,
533    pub consecutive_failures: usize,
534    pub consecutive_successes: usize,
535}
536
537#[no_mangle]
538pub extern "C" fn ri_circuit_breaker_get_stats(
539    cb: *mut CRiCircuitBreaker,
540    out_stats: *mut CRiCircuitBreakerMetrics,
541) -> c_int {
542    if cb.is_null() || out_stats.is_null() {
543        return -1;
544    }
545
546    unsafe {
547        let stats = (*cb).inner.get_stats();
548        let state = match stats.state.as_str() {
549            "Closed" => RI_CB_STATE_CLOSED,
550            "Open" => RI_CB_STATE_OPEN,
551            "HalfOpen" => RI_CB_STATE_HALF_OPEN,
552            _ => RI_CB_STATE_CLOSED,
553        };
554
555        *out_stats = CRiCircuitBreakerMetrics {
556            state,
557            failure_count: stats.failure_count,
558            success_count: stats.success_count,
559            consecutive_failures: stats.consecutive_failures,
560            consecutive_successes: stats.consecutive_successes,
561        };
562        0
563    }
564}
565
566#[no_mangle]
567pub extern "C" fn ri_circuit_breaker_reset(cb: *mut CRiCircuitBreaker) {
568    if cb.is_null() {
569        return;
570    }
571    unsafe {
572        (*cb).inner.reset();
573    }
574}
575
576#[no_mangle]
577pub extern "C" fn ri_circuit_breaker_force_open(cb: *mut CRiCircuitBreaker) {
578    if cb.is_null() {
579        return;
580    }
581    unsafe {
582        (*cb).inner.force_open();
583    }
584}
585
586#[no_mangle]
587pub extern "C" fn ri_circuit_breaker_force_close(cb: *mut CRiCircuitBreaker) {
588    if cb.is_null() {
589        return;
590    }
591    unsafe {
592        (*cb).inner.force_close();
593    }
594}
595
596pub const RI_LB_STRATEGY_ROUND_ROBIN: c_int = 0;
597pub const RI_LB_STRATEGY_WEIGHTED_ROUND_ROBIN: c_int = 1;
598pub const RI_LB_STRATEGY_LEAST_CONNECTIONS: c_int = 2;
599pub const RI_LB_STRATEGY_RANDOM: c_int = 3;
600pub const RI_LB_STRATEGY_IP_HASH: c_int = 4;
601pub const RI_LB_STRATEGY_LEAST_RESPONSE_TIME: c_int = 5;
602pub const RI_LB_STRATEGY_CONSISTENT_HASH: c_int = 6;
603
604fn strategy_from_c_int(strategy: c_int) -> RiLoadBalancerStrategy {
605    match strategy {
606        RI_LB_STRATEGY_ROUND_ROBIN => RiLoadBalancerStrategy::RoundRobin,
607        RI_LB_STRATEGY_WEIGHTED_ROUND_ROBIN => RiLoadBalancerStrategy::WeightedRoundRobin,
608        RI_LB_STRATEGY_LEAST_CONNECTIONS => RiLoadBalancerStrategy::LeastConnections,
609        RI_LB_STRATEGY_RANDOM => RiLoadBalancerStrategy::Random,
610        RI_LB_STRATEGY_IP_HASH => RiLoadBalancerStrategy::IpHash,
611        RI_LB_STRATEGY_LEAST_RESPONSE_TIME => RiLoadBalancerStrategy::LeastResponseTime,
612        RI_LB_STRATEGY_CONSISTENT_HASH => RiLoadBalancerStrategy::ConsistentHash,
613        _ => RiLoadBalancerStrategy::RoundRobin,
614    }
615}
616
617#[no_mangle]
618pub extern "C" fn ri_load_balancer_new(strategy: c_int) -> *mut CRiLoadBalancer {
619    let lb = RiLoadBalancer::new(strategy_from_c_int(strategy));
620    Box::into_raw(Box::new(CRiLoadBalancer::new(lb)))
621}
622
623#[no_mangle]
624pub extern "C" fn ri_load_balancer_free(lb: *mut CRiLoadBalancer) {
625    if !lb.is_null() {
626        unsafe {
627            let _ = Box::from_raw(lb);
628        }
629    }
630}
631
632#[no_mangle]
633pub extern "C" fn ri_load_balancer_add_server(
634    lb: *mut CRiLoadBalancer,
635    id: *const c_char,
636    url: *const c_char,
637    weight: u32,
638    max_connections: usize,
639) -> c_int {
640    if lb.is_null() || id.is_null() || url.is_null() {
641        return -1;
642    }
643
644    unsafe {
645        let id_str = match std::ffi::CStr::from_ptr(id).to_str() {
646            Ok(s) => s,
647            Err(_) => return -2,
648        };
649
650        let url_str = match std::ffi::CStr::from_ptr(url).to_str() {
651            Ok(s) => s,
652            Err(_) => return -3,
653        };
654
655        let server = RiBackendServer {
656            id: id_str.to_string(),
657            url: url_str.to_string(),
658            weight,
659            max_connections,
660            health_check_path: "/health".to_string(),
661            is_healthy: true,
662        };
663
664        let rt = match tokio::runtime::Runtime::new() {
665            Ok(r) => r,
666            Err(_) => return -4,
667        };
668
669        rt.block_on(async {
670            let _ = (*lb).inner.add_server(server).await;
671        });
672
673        0
674    }
675}
676
677#[no_mangle]
678pub extern "C" fn ri_load_balancer_remove_server(
679    lb: *mut CRiLoadBalancer,
680    id: *const c_char,
681) -> c_int {
682    if lb.is_null() || id.is_null() {
683        return -1;
684    }
685
686    unsafe {
687        let id_str = match std::ffi::CStr::from_ptr(id).to_str() {
688            Ok(s) => s,
689            Err(_) => return -2,
690        };
691
692        let rt = match tokio::runtime::Runtime::new() {
693            Ok(r) => r,
694            Err(_) => return -3,
695        };
696
697        let removed = rt.block_on(async {
698            (*lb).inner.remove_server(id_str).await
699        });
700
701        if removed { 0 } else { 1 }
702    }
703}
704
705#[repr(C)]
706pub struct CRiBackendServer {
707    pub id: *mut c_char,
708    pub url: *mut c_char,
709    pub weight: u32,
710    pub max_connections: usize,
711    pub is_healthy: c_int,
712}
713
714#[no_mangle]
715pub extern "C" fn ri_load_balancer_select_server(
716    lb: *mut CRiLoadBalancer,
717    client_ip: *const c_char,
718    out_server: *mut CRiBackendServer,
719) -> c_int {
720    if lb.is_null() || out_server.is_null() {
721        return -1;
722    }
723
724    unsafe {
725        let client_ip_str = if client_ip.is_null() {
726            None
727        } else {
728            match std::ffi::CStr::from_ptr(client_ip).to_str() {
729                Ok(s) => Some(s),
730                Err(_) => None,
731            }
732        };
733
734        let rt = match tokio::runtime::Runtime::new() {
735            Ok(r) => r,
736            Err(_) => return -2,
737        };
738
739        let result = rt.block_on(async {
740            (*lb).inner.select_server(client_ip_str).await
741        });
742
743        match result {
744            Ok(server) => {
745                let id = match std::ffi::CString::new(server.id.clone()) {
746                    Ok(s) => s.into_raw(),
747                    Err(_) => return -3,
748                };
749
750                let url = match std::ffi::CString::new(server.url.clone()) {
751                    Ok(s) => s.into_raw(),
752                    Err(_) => {
753                        let _ = std::ffi::CString::from_raw(id);
754                        return -4;
755                    }
756                };
757
758                *out_server = CRiBackendServer {
759                    id,
760                    url,
761                    weight: server.weight,
762                    max_connections: server.max_connections,
763                    is_healthy: if server.is_healthy { 1 } else { 0 },
764                };
765                0
766            }
767            Err(_) => -5,
768        }
769    }
770}
771
772#[no_mangle]
773pub extern "C" fn ri_backend_server_free(server: *mut CRiBackendServer) {
774    if server.is_null() {
775        return;
776    }
777
778    unsafe {
779        let server = Box::from_raw(server);
780        if !server.id.is_null() {
781            let _ = std::ffi::CString::from_raw(server.id);
782        }
783        if !server.url.is_null() {
784            let _ = std::ffi::CString::from_raw(server.url);
785        }
786    }
787}
788
789#[no_mangle]
790pub extern "C" fn ri_load_balancer_release_server(
791    lb: *mut CRiLoadBalancer,
792    server_id: *const c_char,
793) {
794    if lb.is_null() || server_id.is_null() {
795        return;
796    }
797
798    unsafe {
799        let id_str = match std::ffi::CStr::from_ptr(server_id).to_str() {
800            Ok(s) => s,
801            Err(_) => return,
802        };
803
804        let rt = match tokio::runtime::Runtime::new() {
805            Ok(r) => r,
806            Err(_) => return,
807        };
808
809        rt.block_on(async {
810            (*lb).inner.release_server(id_str).await;
811        });
812    }
813}
814
815#[no_mangle]
816pub extern "C" fn ri_load_balancer_mark_healthy(
817    lb: *mut CRiLoadBalancer,
818    server_id: *const c_char,
819    healthy: c_int,
820) {
821    if lb.is_null() || server_id.is_null() {
822        return;
823    }
824
825    unsafe {
826        let id_str = match std::ffi::CStr::from_ptr(server_id).to_str() {
827            Ok(s) => s,
828            Err(_) => return,
829        };
830
831        let rt = match tokio::runtime::Runtime::new() {
832            Ok(r) => r,
833            Err(_) => return,
834        };
835
836        rt.block_on(async {
837            (*lb).inner.mark_server_healthy(id_str, healthy != 0).await;
838        });
839    }
840}
841
842#[repr(C)]
843pub struct CRiLoadBalancerServerStats {
844    pub active_connections: usize,
845    pub total_requests: usize,
846    pub failed_requests: usize,
847    pub response_time_ms: usize,
848}
849
850#[no_mangle]
851pub extern "C" fn ri_load_balancer_get_server_stats(
852    lb: *mut CRiLoadBalancer,
853    server_id: *const c_char,
854    out_stats: *mut CRiLoadBalancerServerStats,
855) -> c_int {
856    if lb.is_null() || server_id.is_null() || out_stats.is_null() {
857        return -1;
858    }
859
860    unsafe {
861        let id_str = match std::ffi::CStr::from_ptr(server_id).to_str() {
862            Ok(s) => s,
863            Err(_) => return -2,
864        };
865
866        let rt = match tokio::runtime::Runtime::new() {
867            Ok(r) => r,
868            Err(_) => return -3,
869        };
870
871        let result = rt.block_on(async {
872            (*lb).inner.get_server_stats(id_str).await
873        });
874
875        match result {
876            Some(stats) => {
877                *out_stats = CRiLoadBalancerServerStats {
878                    active_connections: stats.active_connections,
879                    total_requests: stats.total_requests,
880                    failed_requests: stats.failed_requests,
881                    response_time_ms: stats.response_time_ms,
882                };
883                0
884            }
885            None => -4,
886        }
887    }
888}
889
890#[no_mangle]
891pub extern "C" fn ri_load_balancer_get_server_count(lb: *mut CRiLoadBalancer) -> usize {
892    if lb.is_null() {
893        return 0;
894    }
895
896    let rt = match tokio::runtime::Runtime::new() {
897        Ok(r) => r,
898        Err(_) => return 0,
899    };
900
901    unsafe {
902        rt.block_on(async {
903            (*lb).inner.get_server_count().await
904        })
905    }
906}
907
908#[no_mangle]
909pub extern "C" fn ri_load_balancer_get_healthy_count(lb: *mut CRiLoadBalancer) -> usize {
910    if lb.is_null() {
911        return 0;
912    }
913
914    let rt = match tokio::runtime::Runtime::new() {
915        Ok(r) => r,
916        Err(_) => return 0,
917    };
918
919    unsafe {
920        rt.block_on(async {
921            (*lb).inner.get_healthy_server_count().await
922        })
923    }
924}