目录
前言
发送过去后,在后端是如何处理请求,如何调用并返回结果?
这篇就来看看这个核心的问题
正文
随便看看
解析请求核心源代码如下——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
结果如下
居然搜到了,进去看看
在如下代码调用了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。虽然不知道干什么。
里面有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>>{
//获取manger
let manager = self.manager_owned();
// 判断invoke_key
let expected = manager.invoke_key();
....
// 初始化resolver
let resolver = ...
// 初始化message
let 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,返回bool
let 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一直往下走
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) -> Self
where
F: Fn(Invoke<R>) -> bool + Send + Sync + 'static,
{
self.invoke_handler = Box::new(invoke_handler);
self
}
不必细说。
运行通信函数
现在把Invoke传进来了,至于内部又是如何运行的,可参考如下
返回的处理
调用完闭包,还是是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