模塊地址:https://github.com/netwarps/libp2p-rs/tree/master/infoserver
在上一篇文章的末尾有提到,會採用web server的方式提供相關的restful api,可以在外部觀測網絡收發包的情況。目前已設計完成,在這裏簡單分享一下設計過程。
實現構想
設計Metric時,爲了減少與swarm通信的次數,我們在control中放了一份metric的clone。對於api server來說,我們完全可以藉助control提供的metric相關操作方法,獲得我們想要得到的網絡流量數據,以及當前連接的一些相關情況。
框架介紹
Tide作爲rust的一個web應用框架,實現了一系列相關的路由功能,可以很方便地構建API;同時,serde的序列化/反序列化功能,能幫助我們將數據格式化成json類型,更容易閱讀和解析。
路由註冊
以get方法爲例,在Tide中,通過以下這種方式實現路由註冊:
server.at(path).get(method)
at方法和get方法如下所示:
// self爲server對象
pub fn at<'a>(&'a mut self, path: &str) -> Route<'a, State> {
let router = Arc::get_mut(&mut self.router)
.expect("Registering routes is not possible after the Server has started");
Route::new(router, path.to_owned())
}
// self爲Route對象
pub fn get(&mut self, ep: impl Endpoint<State>) -> &mut Self {
self.method(http_types::Method::Get, ep);
self
}
可以看到,method參數實際上是一個impl Trait。
在這個實現了這個trait的類型中,有這樣一種形式:
#[async_trait]
impl<State, F, Fut, Res> Endpoint<State> for F
where
State: Clone + Send + Sync + 'static,
F: Send + Sync + 'static + Fn(Request<State>) -> Fut,
Fut: Future<Output = Result<Res>> + Send + 'static,
Res: Into<Response> + 'static,
{
async fn call(&self, req: Request<State>) -> crate::Result {
let fut = (self)(req);
let res = fut.await?;
Ok(res.into())
}
}
對應到我們的代碼中,泛型State爲Control,Fn我們可以實現爲一個async的方法,傳入參數是Request,返回值類型爲tide::Result。
方法分析
以獲取NetworkInfo的代碼進行分析:
- 從request中取出Control,由於下一步需要可變引用,所以這裏要進行clone。
- 調用control的retrieve_info()獲取NetworkInfo數據。
- 由於ConnectionInfo包含了PeerId,而PeerId底層的Multihash尚未支持serde,因此在這裏新建了NetworkConnectionInfo這個struct,PeerId設置爲String類型,即可實現serde的格式化操作。
- 迭代network_info的connect_info,得到的vector與其他數據組合生成NetworkConnectionStatus。
-
調用Body::from_json()將數據格式化成json,作爲body返回。
/// Get connection info async fn get_connection_info(req: Request<Control>) -> tide::Result { let mut control = req.state().clone(); let network_info = control.retrieve_networkinfo().await.map_err(|e| { log::error!("{:?}", e); tide::Error::new(500, e) })?; let mut connection_info = Vec::new(); for item in network_info.connection_info.iter() { let info = NetworkConnectionInfo { la: item.la.to_vec(), ra: item.ra.to_vec(), local_peer_id: item.local_peer_id.to_string(), remote_peer_id: item.remote_peer_id.to_string(), num_inbound_streams: item.num_inbound_streams, num_outbound_streams: item.num_outbound_streams, }; connection_info.push(info); } let network_connection_status = NetworkConnectionStatus { num_connections: network_info.num_connections, num_connections_pending: network_info.num_connections_pending, num_connections_established: network_info.num_connections_established, num_active_streams: network_info.num_active_streams, connection_info, }; let result_body = Body::from_json(&ResponseBody { status: 0, message: "".to_string(), result: vec![serde_json::to_string(&network_connection_status).unwrap()], })?; let response = Response::builder(200).body(result_body).build(); Ok(response) }
接口列表
目前所實現的接口有如下幾個:
無參數接口
127.0.0.1:8999
127.0.0.1:8999/recv
127.0.0.1:8999/send
127.0.0.1:8999/peer
127.0.0.1:8999/connection
帶參數接口
127.0.0.1:8999/peer/_
127.0.0.1:8999/protocol?protocol_id=_
其中,帶參數的peer接口意爲需要傳遞一個具體的PeerID。<br>
而ProtocolID則使用param的方式進行傳遞。
未解難點
在設計路由註冊時,有嘗試過這麼一種方式:生成一個HashMap常量,key爲path,value爲method,統一管理所有的路由。執行new()方法的時候,迭代這個hashmap,將路由信息註冊到server中。
這個方法的難點在於,我們的method實際上是一個返回值類型爲future的閉包。假設以閉包的形式作爲value,編譯器會提示以下錯誤:
`impl Trait` not allowed outside of function and inherent method return types
意思是impl Trait無法作爲函數以外的返回值類型。
如果value以動態分派作爲類型,意味着我們需要以Box<dyn Endpoint<State>>作爲value類型。對於HashMap而言,除非直接消耗掉,不然從中取出的數據都是引用類型的,而clone方法在此處似乎也是行不通的,返回的仍然是一個Box的引用。目前所採用的路由註冊方式,在代碼的閱讀上不太友好,後續考慮用其他方式進行優化。
部分效果展示
當前節點向某個目標節點發送字節數(out)和從目標節點獲取的字節數(in)大小:
當前節點使用/ipfs/id/1.0.0協議所發送(out)和接收(in)的數據包字節大小:
Netwarps 由國內資深的雲計算和分佈式技術開發團隊組成,該團隊在金融、電力、通信及互聯網行業有非常豐富的落地經驗。Netwarps 目前在深圳、北京均設立了研發中心,團隊規模30+,其中大部分爲具備十年以上開發經驗的技術人員,分別來自互聯網、金融、雲計算、區塊鏈以及科研機構等專業領域。
Netwarps 專注於安全存儲技術產品的研發與應用,主要產品有去中心化文件系統(DFS)、去中心化計算平臺(DCP),致力於提供基於去中心化網絡技術實現的分佈式存儲和分佈式計算平臺,具有高可用、低功耗和低網絡的技術特點,適用於物聯網、工業互聯網等場景。
公衆號:Netwarps