目錄
前言
正文
隨便看看
看看get
看看parse_invoke_request
看看message_handler
看看handle_ipc_message
看看webview的on_message方法
第一種情況的處理
第二種情況的處理
運行通信函數
返回的處理
?整個流程
前言
【Tauri2】033 __TAURI_INTERNALS__和invoke-CSDN博客文章瀏覽閱讀1k次,點贊24次,收藏24次。前面說過許多關于的invoke的事情,有通信函數,fetch、invoke_key等這些。這篇再來看看關于invoke的東西看來內部的通信是通過window.__TAURI_INTERNALS__對象來實現的。注冊通過函數用了聲明宏。筆者發現看github上的源碼,比看打包后的源碼更清晰,以后就使用github上的tauri的源碼了,不錯。哈哈哈哈t=P1C7t=P1C7t=P1C7t=P1C7t=P1C7t=P1C7t=P1C7。https://blog.csdn.net/qq_63401240/article/details/147523382?spm=1001.2014.3001.5502前面介紹了前端invoke本質就是通過fetch發送一個post請求。
發送過去后,在后端是如何處理請求,如何調用并返回結果?
這篇就來看看這個核心的問題
正文
隨便看看
解析請求核心源代碼如下——protocol.rs
tauri/crates/tauri/src/ipc/protocol.rs at dev · tauri-apps/taurihttps://github.com/tauri-apps/tauri/blob/dev/crates/tauri/src/ipc/protocol.rsprotocol.rs主要的方法
看名字,簡單說說
message_handler,消息處理器,不知道干什么
get:得到,得到什么,直覺認為,應該得到請求。
handle_ipc_message :處理ipc消息,干什么,后面再說。
parse_invoke_message:解析invoke消息。
而且,get方法和message_handler方法是pub的
這個pub就非常有趣了。
pub說明可能會被調用,只能可能,不一定。
因此,在github中的tauri源碼中搜索一下
repo:tauri-apps/tauri ipc::protocol
結果如下
居然搜到了,進去看看
tauri/crates/tauri/src/manager/webview.rs at dev · tauri-apps/taurihttps://github.com/tauri-apps/tauri/blob/dev/crates/tauri/src/manager/webview.rs在里面,筆者發現了更多東西
在如下代碼調用了get方法
if !registered_scheme_protocols.contains(&"ipc".into()) {let protocol = crate::ipc::protocol::get(manager.manager_owned());pending.register_uri_scheme_protocol("ipc", move |webview_id, request, responder| {protocol(webview_id, request, UriSchemeResponder(responder))});registered_scheme_protocols.push("ipc".into());}
這段代碼的意思,調用get獲得了protocol ,然后注冊ipc 這個uri_scheme_protocol和scheme_protocol
這個pending是什么?
PendingWebview<EventLoopMessage, R>
可以發現是個?PendingWebview。雖然不知道干什么。
tauri/crates/tauri-runtime/src/webview.rs at dev · tauri-apps/taurihttps://github.com/tauri-apps/tauri/blob/dev/crates/tauri-runtime/src/webview.rs#L82可以找到,就是一個結構體,
里面有uri_scheme_protocols和ipc_handler等之類的字段
還可以發現ipc是個協議,tauri自定義的協議
雖然前面說過
發送的確是post請求,但是并不是使用http協議。
總之,注冊ipc這個protocol。
pending.ipc_handler = Some(crate::ipc::protocol::message_handler(manager.manager_owned(),));
也使用了message_handler。
注冊了這個ipc,發現請求,就會被webview處理。
以及message_handler處理handler
上面還注冊了tauri這個protocol
if !registered_scheme_protocols.contains(&"tauri".into()) {......}
看看get
pub fn get<R: Runtime>(manager: Arc<AppManager<R>>) -> UriSchemeProtocolHandler {Box::new(move |label, request, responder| {#[cfg(feature = "tracing")]let span =...let respond = ...match *request.method() {Method::POST => {if let Some(webview) = manager.get_webview(label) {match parse_invoke_request(&manager, request) {...}} else {....}}Method::OPTIONS => {....}_ => {let mut r = http::Response::new("only POST and OPTIONS are allowed".as_bytes().into());....}}})
}
可以發現,這個get方法
首先判斷request的method,是post請求和option請求,其他請求返回,只能使用post和option。
如果是post請求就獲取webview,然后調用parse_invoke_request方法,處理后續的結果
看看parse_invoke_request
fn parse_invoke_request<R: Runtime>(#[allow(unused_variables)] manager: &AppManager<R>,request: http::Request<Vec<u8>>,
) -> std::result::Result<InvokeRequest, String> {#[allow(unused_mut)]let (parts, mut body) = request.into_parts();let cmd =....let has_payload = !body.is_empty();let invoke_key = parts...let url = Url::parse...let callback = CallbackFn(...)let error = CallbackFn(...)let body = ....let payload = InvokeRequest {cmd,callback,error,url,body,headers: parts.headers,invoke_key,};Ok(payload)
}
筆者刪減了許多,總體上看,就是從request請求中提取數據
cmd、callback、error、url、body、header、invoke_key
將提取到的數據合并成一個InvokeRequest ,返回。
筆者省略了許多東西。
傳入的是http::Request<Vec<u8>>
返回InvokeRequest
總之——將前端的request變成Rust可以處理的InvokeRequest
看看message_handler
pub fn message_handler<R: Runtime>(manager: Arc<AppManager<R>>,
) -> crate::runtime::webview::WebviewIpcHandler<crate::EventLoopMessage, R> {Box::new(move |webview, request| handle_ipc_message(request, &manager, &webview.label))
}
返回WebviewIpcHandler
pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;
WebviewIpcHandler是一個Box,Box指向一個實現了send的動態閉包。
有點復雜。
看看handle_ipc_message
fn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager<R>, label: &str) {if let Some(webview) = manager.get_webview(label) {#[derive(Deserialize, Default)]#[serde(rename_all = "camelCase")]struct RequestOptions {....}#[derive(Deserialize)]struct Message {...}#[allow(unused_mut)]let mut invoke_message: Option<crate::Result<Message>> = None;let message = invoke_message.unwrap_or_else(|| {...serde_json::from_str::<Message>(request.body()).map_err(Into::into)});match message {Ok(message) => {let options = message.options.unwrap_or_default();let request = InvokeRequest {...};webview.on_message(request,Box::new(move |webview, cmd, response, callback, error| {...});}Err(e) => {....}}}
}
傳入了http::Request,
這一段代碼
serde_json::from_str::<Message>(request.body()).map_err(Into::into)
從請求中提取到Message,然后變成InvokeRequest?
這個邏輯和get+parse_invoke_request差不多
獲得了InvokeRequest之后,使用webview的on_message方法
實際上在get中也是這樣的,從?parse_invoke_request返回InvokeRequest,然后使用。
看看webview的on_message方法
pub fn on_message(self, request: InvokeRequest, responder: Box<OwnedInvokeResponder<R>>{//獲取mangerlet manager = self.manager_owned();// 判斷invoke_keylet expected = manager.invoke_key();....// 初始化resolverlet resolver = ...// 初始化messagelet message = InvokeMessage...);//判斷請求的來源let acl_origin = ...let (resolved_acl, has_app_acl_manifest) = ...// 初始化Invoke let mut invoke = Invoke {message,resolver: resolver.clone(),acl: resolved_acl,};// 獲取插件名字和cmd的名字let plugin_command = request.cmd.strip_prefix("plugin:").map(|raw_command| {...(plugin, command)});// 判斷插件是否存在以及相關權限if (plugin_command.is_some() || has_app_acl_manifest){...}
將InvokeRequest傳進來之后,
前面一大推都是在處理初始化和權限問題。
現在獲得了plugin_command?
后面就要對plugin_command?進行操作了
if let Some((plugin, command_name)) = plugin_command {...} else {...}
然后就分成了兩部分,因為cmd有兩種情況
1、插件和插件的cmd,比如plugin:window|theme
2、自定義的cmd,比如greet
分別處理這兩種情況
第一種情況的處理
// 為invoke的command設置要執行cmd的名字invoke.message.command = command_name;// 克隆一下,后面會發生所有權的轉移,無法使用command let command = invoke.message.command.clone();#[cfg(mobile)]let message = invoke.message.clone();#[allow(unused_mut)] // 執行cmd,返回boollet mut handled = manager.extend_api(plugin, invoke);#[cfg(mobile)]{// 移動端的插件 }// 不是true,說明沒找到命令if !handled {resolver.reject(format!("Command {command} not found"));}
核心處理是這一行代碼,把plugin和invoke傳進去
let mut handled = manager.extend_api(plugin, invoke);
對extend_api一直往下走
tauri/crates/tauri/src/plugin.rs at dev · tauri-apps/taurihttps://github.com/tauri-apps/tauri/blob/dev/crates/tauri/src/plugin.rs#L777發現如下代碼
fn extend_api(&mut self, invoke: Invoke<R>) -> bool {(self.invoke_handler)(invoke)}
里面的代碼就是執行結構體TauriPlugin中的invoke_handler方法中的閉包。
感覺有點繞口,總之,執行閉包。
為什么是這樣寫的代碼?(self.invoke_handler)(invoke)
看看插件中的invoke_handler的定義
invoke_handler: Box<InvokeHandler<R>>,
pub type InvokeHandler<R> = dyn Fn(Invoke<R>) -> bool + Send + Sync + 'static;
提取關鍵的部分,如下
Box<dyn Fn>
這表示一種動態分發的函數類型。簡單地說
1、左邊的括號是用于獲取Box包裝的閉包
2、右邊括號運行Box里面的閉包
總之,獲取閉包,運行閉包。
舉個簡單地例子
如下代碼,
fn use_world() {type World = Box<dyn Fn(String)>;struct Api {world: World,}let api = Api {world: Box::new(|s| {println!("hello {}", s);}),};(api.world)("world".to_string());
}
或者直接使用
fn use_world() {(Box::new(|s|{ println!("hello {}", s);}))("world".to_string());
}
閉包傳參Invoke。沒問題。
第二種情況的處理
只有cmd命令,沒有插件
let command = invoke.message.command.clone();let handled = manager.run_invoke_handler(invoke);if !handled {resolver.reject(format!("Command {command} not found"));}
使用的是run_invoke_handler這個函數
pub fn run_invoke_handler(&self, invoke: Invoke<R>) -> bool {(self.webview.invoke_handler)(invoke)}
一模一樣,不必細說
再看看注冊通信函數用的invoke_handler
#[must_use]pub fn invoke_handler<F>(mut self, invoke_handler: F) -> SelfwhereF: Fn(Invoke<R>) -> bool + Send + Sync + 'static,{self.invoke_handler = Box::new(invoke_handler);self}
不必細說。?
運行通信函數
現在把Invoke傳進來了,至于內部又是如何運行的,可參考如下
【Tauri2】005——tauri::command屬性與invoke函數_tauri invoke-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146581991?spm=1001.2014.3001.5502
【Tauri2】007——Tauri2和cargo expand-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146632055?spm=1001.2014.3001.5502不必細說。
返回的處理
調用完閉包,還是是on_message這個函數中處理結果,返回給前端
webview.on_message(request,Box::new(move |_webview, _cmd, response, _callback, _error| {...respond(response);}),);
調用的respond 或者使用from_callback_fn、responder_eval
match response {....Channel::from_callback_fn(...);} else {....responder_eval(...)}}
這就不細說,就是返回結果,比如需要考慮返回的字符串、還是使用回調函數callback、還是二進制數組等的原始數據等。?
比如事件Event,就需要callback
返回不一樣,處理不一樣。
fn responder_eval<R: Runtime>(webview: &crate::Webview<R>,js: crate::Result<String>,error: CallbackFn,)
從 responder_eval的函數簽名中可以看出,好像還是處理錯誤的回調函數。
?整個流程
綜上所述,簡單地說,后端解析的流程如下
??????? -> invoke
??????? -> windon._TAURI_INTERNALS__.invoke
??????? -> fetch
????????-> parse_invoke_request / handle_ipc_message?
????????-> on_message?
????????->?extend_api /?? run_invoke_handler
??????? -> invoke_handler
????????->?response