Skip to main content

rings_node/native/endpoint/
mod.rs

1//! rings-node service run with `Swarm` and chord stabilization.
2mod http_error;
3mod ws;
4
5use std::net::SocketAddr;
6use std::sync::Arc;
7
8use axum::extract::ConnectInfo;
9use axum::extract::Request;
10use axum::extract::State;
11use axum::extract::WebSocketUpgrade;
12use axum::http::HeaderValue;
13use axum::response::IntoResponse;
14use axum::routing::get;
15use axum::routing::post;
16use axum::Router;
17use jsonrpc_core::MetaIoHandler;
18use rings_gateway::GatewayStatus;
19use rings_gateway::GatewayStatusHandle;
20use rings_rpc::protos::rings_node::NodeInfoResponse;
21use tokio::net::TcpListener;
22use tower_http::cors::CorsLayer;
23
24use self::http_error::HttpError;
25use crate::processor::Processor;
26
27/// JSON-RPC state
28#[derive(Clone)]
29pub struct JsonRpcState<M>
30where M: jsonrpc_core::Middleware<Arc<Processor>>
31{
32    processor: Arc<Processor>,
33    io_handler: MetaIoHandler<Arc<Processor>, M>,
34}
35
36/// websocket state
37#[derive(Clone)]
38#[allow(dead_code)]
39pub struct WsState {
40    processor: Arc<Processor>,
41}
42
43/// Status state
44#[derive(Clone)]
45pub struct StatusState {
46    processor: Arc<Processor>,
47}
48
49/// Gateway status endpoint state.
50#[derive(Clone)]
51pub struct GatewayStatusState {
52    status: GatewayStatusHandle,
53}
54
55struct ExternalRpcMiddleware;
56struct InternalRpcMiddleware;
57
58/// Run a web server to handle jsonrpc request locally
59pub async fn run_internal_api(port: u16, processor: Arc<Processor>) -> anyhow::Result<()> {
60    run_internal_api_with_gateway(port, processor, None).await
61}
62
63/// Run the local JSON-RPC server with an optional foreground-gateway status endpoint.
64pub async fn run_internal_api_with_gateway(
65    port: u16,
66    processor: Arc<Processor>,
67    gateway: Option<GatewayStatusHandle>,
68) -> anyhow::Result<()> {
69    let gateway_configured = gateway.is_some();
70    let binding_addr = SocketAddr::from(([127, 0, 0, 1], port));
71
72    let jsonrpc_handler = MetaIoHandler::with_middleware(InternalRpcMiddleware);
73    let jsonrpc_state = Arc::new(JsonRpcState {
74        processor: processor.clone(),
75        io_handler: jsonrpc_handler,
76    });
77
78    let ws_state = Arc::new(WsState {
79        processor: processor.clone(),
80    });
81
82    let status_state = Arc::new(StatusState { processor });
83
84    let mut router = Router::new()
85        .route(
86            "/",
87            post(jsonrpc_io_handler).with_state(jsonrpc_state.clone()),
88        )
89        .route("/ws", get(ws_handler).with_state(ws_state))
90        .route("/status", get(status_handler).with_state(status_state));
91    if let Some(status) = gateway {
92        router = router.route(
93            "/gateway/status",
94            get(gateway_status_handler).with_state(Arc::new(GatewayStatusState { status })),
95        );
96    }
97    let axum_make_service = router
98        .layer(CorsLayer::permissive())
99        .layer(axum::middleware::from_fn(node_info_header))
100        .into_make_service_with_connect_info::<SocketAddr>();
101
102    println!("JSON-RPC endpoint: http://{binding_addr}");
103    println!("WebSocket endpoint: http://{binding_addr}/ws");
104    if gateway_configured {
105        println!("Gateway status endpoint: http://{binding_addr}/gateway/status");
106    }
107    let listener = TcpListener::bind(binding_addr).await?;
108    axum::serve(listener, axum_make_service).await?;
109    Ok(())
110}
111
112/// Run a web server to handle jsonrpc request from external
113pub async fn run_external_api(addr: String, processor: Arc<Processor>) -> anyhow::Result<()> {
114    let binding_addr: SocketAddr = addr.parse()?;
115
116    let jsonrpc_handler = MetaIoHandler::with_middleware(ExternalRpcMiddleware);
117    let jsonrpc_state = Arc::new(JsonRpcState {
118        processor: processor.clone(),
119        io_handler: jsonrpc_handler,
120    });
121
122    let status_state = Arc::new(StatusState { processor });
123
124    let router = Router::new()
125        .route(
126            "/",
127            post(jsonrpc_io_handler).with_state(jsonrpc_state.clone()),
128        )
129        .route("/status", get(status_handler).with_state(status_state));
130    let axum_make_service = router
131        .layer(CorsLayer::permissive())
132        .layer(axum::middleware::from_fn(node_info_header))
133        .into_make_service_with_connect_info::<SocketAddr>();
134
135    println!("JSON-RPC endpoint: http://{addr}");
136    let listener = TcpListener::bind(binding_addr).await?;
137    axum::serve(listener, axum_make_service).await?;
138    Ok(())
139}
140
141async fn jsonrpc_io_handler<M>(
142    State(state): State<Arc<JsonRpcState<M>>>,
143    body: String,
144) -> Result<JsonResponse, HttpError>
145where
146    M: jsonrpc_core::Middleware<Arc<Processor>>,
147{
148    let r = state
149        .io_handler
150        .handle_request(&body, state.processor.clone())
151        .await
152        .ok_or(HttpError::BadRequest)?;
153    Ok(JsonResponse(r))
154}
155
156async fn node_info_header(req: Request, next: axum::middleware::Next) -> axum::response::Response {
157    let mut res = next.run(req).await;
158    let headers = res.headers_mut();
159
160    if let Ok(version) = HeaderValue::from_str(crate::util::build_version().as_str()) {
161        headers.insert("X-NODE-VERSION", version);
162    }
163    res
164}
165
166async fn status_handler(
167    State(state): State<Arc<StatusState>>,
168) -> Result<axum::Json<NodeInfoResponse>, HttpError> {
169    let info = state
170        .processor
171        .get_node_info()
172        .await
173        .map_err(|_| HttpError::Internal)?;
174    Ok(axum::Json(info))
175}
176
177async fn gateway_status_handler(
178    State(state): State<Arc<GatewayStatusState>>,
179) -> axum::Json<GatewayStatus> {
180    axum::Json(state.status.snapshot())
181}
182
183/// JSON response struct
184#[derive(Debug, Clone)]
185pub struct JsonResponse(String);
186
187impl IntoResponse for JsonResponse {
188    fn into_response(self) -> axum::response::Response {
189        ([("content-type", "application/json")], self.0).into_response()
190    }
191}
192
193async fn ws_handler(
194    State(state): State<Arc<WsState>>,
195    ws: WebSocketUpgrade,
196    ConnectInfo(addr): ConnectInfo<SocketAddr>,
197) -> impl IntoResponse {
198    tracing::debug!("ws connected, remote: {}", addr);
199    ws.on_upgrade(move |socket| self::ws::handle_socket(state, socket))
200}
201
202mod jsonrpc_middleware_impl {
203    use std::future::Future;
204
205    use jsonrpc_core::futures_util::future;
206    use jsonrpc_core::futures_util::future::Either;
207    use jsonrpc_core::futures_util::FutureExt;
208    use jsonrpc_core::middleware::NoopCallFuture;
209    use jsonrpc_core::middleware::NoopFuture;
210    use jsonrpc_core::*;
211    use rings_rpc::protos::rings_node_handler::ExternalRpcHandler;
212    use rings_rpc::protos::rings_node_handler::InternalRpcHandler;
213
214    use super::*;
215
216    impl Middleware<Arc<Processor>> for InternalRpcMiddleware {
217        type Future = NoopFuture;
218        type CallFuture = NoopCallFuture;
219
220        fn on_call<F, X>(
221            &self,
222            call: Call,
223            meta: Arc<Processor>,
224            next: F,
225        ) -> Either<Self::CallFuture, X>
226        where
227            F: Fn(Call, Arc<Processor>) -> X + Send + Sync,
228            X: Future<Output = Option<Output>> + Send + 'static,
229        {
230            match call {
231                Call::MethodCall(req) => {
232                    let fut = InternalRpcHandler
233                        .handle_request(meta, req.method, req.params.into())
234                        .then(move |res| {
235                            future::ready(Some(Output::from(res, req.id, req.jsonrpc)))
236                        });
237                    Either::Left(Box::pin(fut))
238                }
239                _ => Either::Left(Box::pin(next(call, meta))),
240            }
241        }
242    }
243
244    impl Middleware<Arc<Processor>> for ExternalRpcMiddleware {
245        type Future = NoopFuture;
246        type CallFuture = NoopCallFuture;
247
248        fn on_call<F, X>(
249            &self,
250            call: Call,
251            meta: Arc<Processor>,
252            next: F,
253        ) -> Either<Self::CallFuture, X>
254        where
255            F: Fn(Call, Arc<Processor>) -> X + Send + Sync,
256            X: Future<Output = Option<Output>> + Send + 'static,
257        {
258            match call {
259                Call::MethodCall(req) => {
260                    let fut = ExternalRpcHandler
261                        .handle_request(meta, req.method, req.params.into())
262                        .then(move |res| {
263                            future::ready(Some(Output::from(res, req.id, req.jsonrpc)))
264                        });
265                    Either::Left(Box::pin(fut))
266                }
267                _ => Either::Left(Box::pin(next(call, meta))),
268            }
269        }
270    }
271}