futu_grpc/server/
startup.rs1use std::sync::Arc;
2
3use tokio::sync::watch;
4
5use futu_auth::{KeyStore, RuntimeCounters};
6use futu_server::router::RequestRouter;
7
8use crate::proto::futu_open_d_server::FutuOpenDServer;
9
10use super::{FutuGrpcService, GRPC_MAX_MESSAGE_SIZE_BYTES, GrpcPushBroadcaster};
11
12#[cfg(test)]
14#[allow(dead_code)]
15pub(super) fn build_service(
16 router: Arc<RequestRouter>,
17 push_broadcaster: Arc<GrpcPushBroadcaster>,
18) -> FutuOpenDServer<FutuGrpcService> {
19 apply_grpc_message_limits(FutuOpenDServer::new(FutuGrpcService::new(
20 router,
21 push_broadcaster,
22 )))
23}
24
25pub fn build_service_with_auth(
27 router: Arc<RequestRouter>,
28 push_broadcaster: Arc<GrpcPushBroadcaster>,
29 key_store: Arc<KeyStore>,
30 counters: Arc<RuntimeCounters>,
31) -> FutuOpenDServer<FutuGrpcService> {
32 apply_grpc_message_limits(FutuOpenDServer::new(FutuGrpcService::with_auth(
33 router,
34 push_broadcaster,
35 key_store,
36 counters,
37 )))
38}
39
40fn apply_grpc_message_limits(
41 service: FutuOpenDServer<FutuGrpcService>,
42) -> FutuOpenDServer<FutuGrpcService> {
43 service
44 .max_decoding_message_size(GRPC_MAX_MESSAGE_SIZE_BYTES)
45 .max_encoding_message_size(GRPC_MAX_MESSAGE_SIZE_BYTES)
46}
47
48pub async fn start(
50 listen_addr: &str,
51 router: Arc<RequestRouter>,
52 push_broadcaster: Arc<GrpcPushBroadcaster>,
53) -> Result<(), Box<dyn std::error::Error>> {
54 start_with_auth(
55 listen_addr,
56 router,
57 push_broadcaster,
58 Arc::new(KeyStore::empty()),
59 Arc::new(RuntimeCounters::new()),
60 )
61 .await
62}
63
64pub async fn start_with_auth(
66 listen_addr: &str,
67 router: Arc<RequestRouter>,
68 push_broadcaster: Arc<GrpcPushBroadcaster>,
69 key_store: Arc<KeyStore>,
70 counters: Arc<RuntimeCounters>,
71) -> Result<(), Box<dyn std::error::Error>> {
72 let addr = listen_addr
73 .parse()
74 .map_err(|e| format!("invalid addr: {e}"))?;
75 if !key_store.is_configured() {
76 tracing::warn!(
77 "gRPC server running WITHOUT API key auth (legacy mode); \
78 all RPCs are open. Pass --grpc-keys-file to enable scope-based auth."
79 );
80 }
81 let service = build_service_with_auth(router, push_broadcaster, key_store, counters);
82 tracing::info!(addr = %listen_addr, "gRPC 服务已启动");
83 tonic::transport::Server::builder()
84 .add_service(service)
85 .serve(addr)
86 .await
87 .map_err(|error| {
88 std::io::Error::other(futu_server::bind_hint::bind_error_message(
89 "gRPC",
90 "--grpc-port",
91 listen_addr,
92 error,
93 ))
94 })?;
95 Ok(())
96}
97
98pub async fn start_with_auth_until_shutdown(
101 listen_addr: &str,
102 router: Arc<RequestRouter>,
103 push_broadcaster: Arc<GrpcPushBroadcaster>,
104 key_store: Arc<KeyStore>,
105 counters: Arc<RuntimeCounters>,
106 shutdown_rx: watch::Receiver<bool>,
107) -> Result<(), Box<dyn std::error::Error>> {
108 let addr = listen_addr
109 .parse()
110 .map_err(|e| format!("invalid addr: {e}"))?;
111 if !key_store.is_configured() {
112 tracing::warn!(
113 "gRPC server running WITHOUT API key auth (legacy mode); \
114 all RPCs are open. Pass --grpc-keys-file to enable scope-based auth."
115 );
116 }
117 let service = build_service_with_auth(router, push_broadcaster, key_store, counters);
118 tracing::info!(addr = %listen_addr, "gRPC 服务已启动");
119 tonic::transport::Server::builder()
120 .add_service(service)
121 .serve_with_shutdown(addr, grpc_shutdown_requested(shutdown_rx))
122 .await
123 .map_err(|error| {
124 std::io::Error::other(futu_server::bind_hint::bind_error_message(
125 "gRPC",
126 "--grpc-port",
127 listen_addr,
128 error,
129 ))
130 })?;
131 Ok(())
132}
133
134async fn grpc_shutdown_requested(mut shutdown_rx: watch::Receiver<bool>) {
135 loop {
136 if *shutdown_rx.borrow() {
137 tracing::info!("gRPC server stopped by shutdown signal");
138 return;
139 }
140 if shutdown_rx.changed().await.is_err() {
141 tracing::info!("gRPC server stopped after shutdown sender dropped");
142 return;
143 }
144 }
145}