client.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::sync::Arc;
  19. use log::{debug, error};
  20. use smol::{channel, io::BufReader, Executor};
  21. use tinyjson::JsonValue;
  22. use url::Url;
  23. use super::{
  24. common::{
  25. http_read_from_stream_response, http_write_to_stream, read_from_stream, write_to_stream,
  26. INIT_BUF_SIZE, READ_TIMEOUT,
  27. },
  28. jsonrpc::*,
  29. };
  30. use crate::{
  31. net::transport::{Dialer, PtStream},
  32. system::{io_timeout, PublisherPtr, StoppableTask, StoppableTaskPtr},
  33. Error, Result,
  34. };
  35. /// JSON-RPC client implementation using asynchronous channels.
  36. pub struct RpcClient {
  37. /// The channel used to send JSON-RPC request objects.
  38. /// The `bool` marks if we should have a reply read timeout.
  39. req_send: channel::Sender<(JsonRequest, bool)>,
  40. /// The channel used to read the JSON-RPC response object.
  41. rep_recv: channel::Receiver<JsonResult>,
  42. /// The channel used to skip waiting for a JSON-RPC client request
  43. req_skip_send: channel::Sender<()>,
  44. /// The stoppable task pointer, used on [`RpcClient::stop()`]
  45. task: StoppableTaskPtr,
  46. }
  47. impl RpcClient {
  48. /// Instantiate a new JSON-RPC client that connects to the given endpoint.
  49. /// The function takes an `Executor` object, which is needed to start the
  50. /// `StoppableTask` which represents the client-server connection.
  51. pub async fn new(endpoint: Url, ex: Arc<Executor<'_>>) -> Result<Self> {
  52. // Instantiate communication channels
  53. let (req_send, req_recv) = channel::unbounded();
  54. let (rep_send, rep_recv) = channel::unbounded();
  55. let (req_skip_send, req_skip_recv) = channel::unbounded();
  56. // Figure out if we're using HTTP and rewrite the URL accordingly.
  57. let mut dialer_url = endpoint.clone();
  58. if endpoint.scheme().starts_with("http+") {
  59. let scheme = endpoint.scheme().strip_prefix("http+").unwrap();
  60. let url_str = endpoint.as_str().replace(endpoint.scheme(), scheme);
  61. dialer_url = url_str.parse()?;
  62. }
  63. let use_http = endpoint.scheme().starts_with("http+");
  64. // Instantiate Dialer and dial the server
  65. // TODO: Could add a timeout here
  66. let dialer = Dialer::new(dialer_url, None).await?;
  67. let stream = dialer.dial(None).await?;
  68. // Create the StoppableTask running the request-reply loop.
  69. // This represents the actual connection, which can be stopped
  70. // using `RpcClient::stop()`.
  71. let task = StoppableTask::new();
  72. task.clone().start(
  73. Self::reqrep_loop(use_http, stream, rep_send, req_recv, req_skip_recv),
  74. |res| async move {
  75. match res {
  76. Ok(()) | Err(Error::RpcClientStopped) => {}
  77. Err(e) => error!(target: "rpc::client", "[RPC] Client error: {}", e),
  78. }
  79. },
  80. Error::RpcClientStopped,
  81. ex.clone(),
  82. );
  83. Ok(Self { req_send, rep_recv, task, req_skip_send })
  84. }
  85. /// Stop the JSON-RPC client. This will trigger `stop()` on the inner
  86. /// `StoppableTaskPtr` resulting in stopping the internal reqrep loop
  87. /// and therefore closing the connection.
  88. pub async fn stop(&self) {
  89. self.task.stop().await;
  90. }
  91. /// Internal function that loops on a given stream and multiplexes the data
  92. async fn reqrep_loop(
  93. use_http: bool,
  94. stream: Box<dyn PtStream>,
  95. rep_send: channel::Sender<JsonResult>,
  96. req_recv: channel::Receiver<(JsonRequest, bool)>,
  97. req_skip_recv: channel::Receiver<()>,
  98. ) -> Result<()> {
  99. debug!(target: "rpc::client::reqrep_loop()", "Starting reqrep loop");
  100. let (reader, mut writer) = smol::io::split(stream);
  101. let mut reader = BufReader::new(reader);
  102. loop {
  103. let mut buf = Vec::with_capacity(INIT_BUF_SIZE);
  104. let mut with_timeout = false;
  105. // Read an incoming client request, or skip it if triggered from
  106. // a JSONRPC notification subscriber
  107. smol::future::or(
  108. async {
  109. let (request, timeout) = req_recv.recv().await?;
  110. with_timeout = timeout;
  111. let request = JsonResult::Request(request);
  112. if use_http {
  113. http_write_to_stream(&mut writer, &request).await?;
  114. } else {
  115. write_to_stream(&mut writer, &request).await?;
  116. }
  117. Ok::<(), crate::Error>(())
  118. },
  119. async {
  120. req_skip_recv.recv().await?;
  121. Ok::<(), crate::Error>(())
  122. },
  123. )
  124. .await?;
  125. if with_timeout {
  126. if use_http {
  127. let _ = io_timeout(
  128. READ_TIMEOUT,
  129. http_read_from_stream_response(&mut reader, &mut buf),
  130. )
  131. .await?;
  132. } else {
  133. let _ =
  134. io_timeout(READ_TIMEOUT, read_from_stream(&mut reader, &mut buf)).await?;
  135. }
  136. } else {
  137. #[allow(clippy::collapsible_else_if)]
  138. if use_http {
  139. let _ = http_read_from_stream_response(&mut reader, &mut buf).await?;
  140. } else {
  141. let _ = read_from_stream(&mut reader, &mut buf).await?;
  142. }
  143. }
  144. let val: JsonValue = String::from_utf8(buf)?.parse()?;
  145. let rep = JsonResult::try_from_value(&val)?;
  146. rep_send.send(rep).await?;
  147. }
  148. }
  149. /// Send a given JSON-RPC request over the instantiated client and
  150. /// return a possible result. If the response is an error, returns
  151. /// a `JsonRpcError`.
  152. pub async fn request(&self, req: JsonRequest) -> Result<JsonValue> {
  153. let req_id = req.id;
  154. debug!(target: "rpc::client", "--> {}", req.stringify()?);
  155. // If the connection is closed, the sender will get an error
  156. // for sending to a closed channel.
  157. self.req_send.send((req, true)).await?;
  158. // If the connection is closed, the receiver will get an error
  159. // for waiting on a closed channel.
  160. let reply = self.rep_recv.recv().await?;
  161. // Handle the response
  162. match reply {
  163. JsonResult::Response(rep) | JsonResult::SubscriberWithReply(_, rep) => {
  164. debug!(target: "rpc::client", "<-- {}", rep.stringify()?);
  165. // Check if the IDs match
  166. if req_id != rep.id {
  167. let e = JsonError::new(ErrorCode::IdMismatch, None, rep.id);
  168. return Err(Error::JsonRpcError((e.error.code, e.error.message)))
  169. }
  170. Ok(rep.result)
  171. }
  172. JsonResult::Error(e) => {
  173. debug!(target: "rpc::client", "<-- {}", e.stringify()?);
  174. Err(Error::JsonRpcError((e.error.code, e.error.message)))
  175. }
  176. JsonResult::Notification(n) => {
  177. debug!(target: "rpc::client", "<-- {}", n.stringify()?);
  178. let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
  179. Err(Error::JsonRpcError((e.error.code, e.error.message)))
  180. }
  181. JsonResult::Request(r) => {
  182. debug!(target: "rpc::client", "<-- {}", r.stringify()?);
  183. let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
  184. Err(Error::JsonRpcError((e.error.code, e.error.message)))
  185. }
  186. JsonResult::Subscriber(_) => {
  187. // When?
  188. let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
  189. Err(Error::JsonRpcError((e.error.code, e.error.message)))
  190. }
  191. }
  192. }
  193. /// Oneshot send a given JSON-RPC request over the instantiated client
  194. /// and immediately close the channels upon receiving a reply.
  195. pub async fn oneshot_request(&self, req: JsonRequest) -> Result<JsonValue> {
  196. let rep = match self.request(req).await {
  197. Ok(v) => v,
  198. Err(e) => {
  199. self.stop().await;
  200. return Err(e)
  201. }
  202. };
  203. self.stop().await;
  204. Ok(rep)
  205. }
  206. /// Listen instantiated client for notifications.
  207. /// NOTE: Subscriber listeners must perform response handling.
  208. pub async fn subscribe(
  209. &self,
  210. req: JsonRequest,
  211. publisher: PublisherPtr<JsonResult>,
  212. ) -> Result<()> {
  213. // Perform initial request
  214. debug!(target: "rpc::client", "--> {}", req.stringify()?);
  215. let req_id = req.id;
  216. // If the connection is closed, the sender will get an error for
  217. // sending to a closed channel.
  218. self.req_send.send((req, false)).await?;
  219. // Now loop and listen to notifications
  220. loop {
  221. // If the connection is closed, the receiver will get an error
  222. // for waiting on a closed channel.
  223. let notification = self.rep_recv.recv().await?;
  224. // Handle the response
  225. match notification {
  226. JsonResult::Notification(ref n) => {
  227. debug!(target: "rpc::client", "<-- {}", n.stringify()?);
  228. self.req_skip_send.send(()).await?;
  229. publisher.notify(notification.clone()).await;
  230. continue
  231. }
  232. JsonResult::Error(e) => {
  233. debug!(target: "rpc::client", "<-- {}", e.stringify()?);
  234. return Err(Error::JsonRpcError((e.error.code, e.error.message)))
  235. }
  236. JsonResult::Response(r) | JsonResult::SubscriberWithReply(_, r) => {
  237. debug!(target: "rpc::client", "<-- {}", r.stringify()?);
  238. let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
  239. return Err(Error::JsonRpcError((e.error.code, e.error.message)))
  240. }
  241. JsonResult::Request(r) => {
  242. debug!(target: "rpc::client", "<-- {}", r.stringify()?);
  243. let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
  244. return Err(Error::JsonRpcError((e.error.code, e.error.message)))
  245. }
  246. JsonResult::Subscriber(_) => {
  247. // When?
  248. let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
  249. return Err(Error::JsonRpcError((e.error.code, e.error.message)))
  250. }
  251. }
  252. }
  253. }
  254. }
  255. /// Highly experimental JSON-RPC client implementation using asynchronous channels,
  256. /// with each new request canceling waiting for the previous one. All requests are
  257. /// executed without a timeout.
  258. pub struct RpcChadClient {
  259. /// The channel used to send JSON-RPC request objects
  260. req_send: channel::Sender<JsonRequest>,
  261. /// The channel used to read the JSON-RPC response object
  262. rep_recv: channel::Receiver<JsonResult>,
  263. /// The stoppable task pointer, used on [`RpcChadClient::stop()`]
  264. task: StoppableTaskPtr,
  265. }
  266. impl RpcChadClient {
  267. /// Instantiate a new JSON-RPC client that connects to the given endpoint.
  268. /// The function takes an `Executor` object, which is needed to start the
  269. /// `StoppableTask` which represents the client-server connection.
  270. pub async fn new(endpoint: Url, ex: Arc<Executor<'_>>) -> Result<Self> {
  271. // Instantiate communication channels
  272. let (req_send, req_recv) = channel::unbounded();
  273. let (rep_send, rep_recv) = channel::unbounded();
  274. // Figure out if we're using HTTP and rewrite the URL accordingly.
  275. let mut dialer_url = endpoint.clone();
  276. if endpoint.scheme().starts_with("http+") {
  277. let scheme = endpoint.scheme().strip_prefix("http+").unwrap();
  278. let url_str = endpoint.as_str().replace(endpoint.scheme(), scheme);
  279. dialer_url = url_str.parse()?;
  280. }
  281. let use_http = endpoint.scheme().starts_with("http+");
  282. // Instantiate Dialer and dial the server
  283. // TODO: Could add a timeout here
  284. let dialer = Dialer::new(dialer_url, None).await?;
  285. let stream = dialer.dial(None).await?;
  286. // Create the StoppableTask running the request-reply loop.
  287. // This represents the actual connection, which can be stopped
  288. // using `RpcChadClient::stop()`.
  289. let task = StoppableTask::new();
  290. task.clone().start(
  291. Self::reqrep_loop(use_http, stream, rep_send, req_recv),
  292. |res| async move {
  293. match res {
  294. Ok(()) | Err(Error::RpcClientStopped) => {}
  295. Err(e) => error!(target: "rpc::chad_client", "[RPC] Client error: {}", e),
  296. }
  297. },
  298. Error::RpcClientStopped,
  299. ex.clone(),
  300. );
  301. Ok(Self { req_send, rep_recv, task })
  302. }
  303. /// Stop the JSON-RPC client. This will trigger `stop()` on the inner
  304. /// `StoppableTaskPtr` resulting in stopping the internal reqrep loop
  305. /// and therefore closing the connection.
  306. pub async fn stop(&self) {
  307. self.task.stop().await;
  308. }
  309. /// Internal function that loops on a given stream and multiplexes the data
  310. async fn reqrep_loop(
  311. use_http: bool,
  312. stream: Box<dyn PtStream>,
  313. rep_send: channel::Sender<JsonResult>,
  314. req_recv: channel::Receiver<JsonRequest>,
  315. ) -> Result<()> {
  316. debug!(target: "rpc::chad_client::reqrep_loop()", "Starting reqrep loop");
  317. let (reader, mut writer) = smol::io::split(stream);
  318. let mut reader = BufReader::new(reader);
  319. loop {
  320. let mut buf = Vec::with_capacity(INIT_BUF_SIZE);
  321. // Read an incoming client request, or wait for a response
  322. smol::future::or(
  323. async {
  324. let request = req_recv.recv().await?;
  325. let request = JsonResult::Request(request);
  326. if use_http {
  327. http_write_to_stream(&mut writer, &request).await?;
  328. } else {
  329. write_to_stream(&mut writer, &request).await?;
  330. }
  331. Ok::<(), crate::Error>(())
  332. },
  333. async {
  334. if use_http {
  335. let _ = http_read_from_stream_response(&mut reader, &mut buf).await?;
  336. } else {
  337. let _ = read_from_stream(&mut reader, &mut buf).await?;
  338. }
  339. let val: JsonValue = String::from_utf8(buf)?.parse()?;
  340. let rep = JsonResult::try_from_value(&val)?;
  341. rep_send.send(rep).await?;
  342. Ok::<(), crate::Error>(())
  343. },
  344. )
  345. .await?;
  346. }
  347. }
  348. /// Send a given JSON-RPC request over the instantiated client and
  349. /// return a possible result. If the response is an error, returns
  350. /// a `JsonRpcError`.
  351. pub async fn request(&self, req: JsonRequest) -> Result<JsonValue> {
  352. // Perform request
  353. let req_id = req.id;
  354. debug!(target: "rpc::chad_client", "--> {}", req.stringify()?);
  355. // If the connection is closed, the sender will get an error
  356. // for sending to a closed channel.
  357. self.req_send.send(req).await?;
  358. // Now loop until we receive our response
  359. loop {
  360. // If the connection is closed, the receiver will get an error
  361. // for waiting on a closed channel.
  362. let reply = self.rep_recv.recv().await?;
  363. // Handle the response
  364. match reply {
  365. JsonResult::Response(rep) | JsonResult::SubscriberWithReply(_, rep) => {
  366. debug!(target: "rpc::chad_client", "<-- {}", rep.stringify()?);
  367. // Check if the IDs match
  368. if req_id != rep.id {
  369. debug!(target: "rpc::chad_client", "Skipping response for request {} as its not our latest({})", rep.id, req_id);
  370. continue
  371. }
  372. return Ok(rep.result)
  373. }
  374. JsonResult::Error(e) => {
  375. debug!(target: "rpc::chad_client", "<-- {}", e.stringify()?);
  376. // Check if the IDs match
  377. if req_id != e.id {
  378. debug!(target: "rpc::chad_client", "Skipping response for request {} as its not our latest({})", e.id, req_id);
  379. continue
  380. }
  381. return Err(Error::JsonRpcError((e.error.code, e.error.message)))
  382. }
  383. JsonResult::Notification(n) => {
  384. debug!(target: "rpc::chad_client", "<-- {}", n.stringify()?);
  385. let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
  386. return Err(Error::JsonRpcError((e.error.code, e.error.message)))
  387. }
  388. JsonResult::Request(r) => {
  389. debug!(target: "rpc::chad_client", "<-- {}", r.stringify()?);
  390. let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
  391. return Err(Error::JsonRpcError((e.error.code, e.error.message)))
  392. }
  393. JsonResult::Subscriber(_) => {
  394. // When?
  395. let e = JsonError::new(ErrorCode::InvalidReply, None, req_id);
  396. return Err(Error::JsonRpcError((e.error.code, e.error.message)))
  397. }
  398. }
  399. }
  400. }
  401. }